diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f3ce984f5e8..a9c6638be27b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - slack: circleci/slack@6.1.1 + slack: circleci/slack@6.1.2 kubernetes: circleci/kubernetes@1.3.1 jobs: # Client-python steps diff --git a/.drone.yml b/.drone.yml index 8cc507030b72..7e5771f8ea2b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -42,7 +42,7 @@ steps: - dependencies-checkout - name: frontend-verify-licenses - image: node:22.21.1 + image: node:22.22.0 volumes: - name: cache-frontend-verify-licenses-yarn path: /root/.yarn/berry @@ -57,7 +57,7 @@ steps: - dependencies-checkout - name: generate-licenses - image: node:22.21.1 + image: node:22.22.0 commands: - chmod 777 scripts/* - ./scripts/generate-licenses.sh @@ -149,7 +149,7 @@ steps: - generate-licenses - name: frontend-e2e-tests - image: node:22.21.1 + image: node:22.22.0 volumes: - name: cache-frontend-e2e-tests-yarn path: /root/.yarn/berry @@ -186,7 +186,7 @@ steps: - frontend-tests - name: upload-build-artefact - image: node:22.21.1 + image: node:22.22.0 failure: ignore when: status: @@ -218,7 +218,7 @@ steps: - api-tests - name: frontend-verify-translation - image: node:22.21.1 + image: node:22.22.0 commands: - cd opencti-platform/opencti-front - node script/verify-translation.js @@ -313,7 +313,7 @@ services: - name: redis image: redis:8.4.0 - name: elastic - image: elasticsearch:8.19.9 + image: elasticsearch:8.19.10 environment: discovery.type: single-node xpack.security.enabled: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 703a27567796..4b2ab6b1b9ef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report OpenCTI about: Create a bug report to help us improve OpenCTI title: '' -labels: bug, needs triage +labels: needs triage assignees: '' type: bug diff --git a/.github/ISSUE_TEMPLATE/bug_report_pycti.yaml b/.github/ISSUE_TEMPLATE/bug_report_pycti.yaml index 287b246ef26a..68357bc9c5ea 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_pycti.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report_pycti.yaml @@ -2,6 +2,7 @@ name: Client Python bug description: Report a bug in the Python client. title: "[BUG] " +type: bug labels: - needs triage - client-python diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index e27532f73994..6cf23b7af9b8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,8 +2,9 @@ name: Feature request OpenCTI about: Ask for a new feature to be implemented in OpenCTI title: '' -labels: needs triage, feature +labels: needs triage assignees: '' +type: feature --- diff --git a/.github/ISSUE_TEMPLATE/feature_request_pycti.yaml b/.github/ISSUE_TEMPLATE/feature_request_pycti.yaml index 3d71ebd41997..034e4d163b4a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request_pycti.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request_pycti.yaml @@ -2,6 +2,7 @@ name: Client Python feature request description: Request a new feature for the Client Python (Pycti) package. title: "[FEATURE] " +type: feature labels: - needs triage - client-python diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..131da2b37018 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,163 @@ +# OpenCTI Copilot Instructions + +## Repository Overview + +OpenCTI is a cyber threat intelligence platform built with a **monorepo structure** containing: +- **opencti-platform/opencti-graphql**: Node.js/TypeScript GraphQL API backend (~60MB) +- **opencti-platform/opencti-front**: React/TypeScript frontend with Relay (~50MB) +- **client-python**: Python library (pycti) for API access (~17MB) +- **opencti-worker**: Python worker for background tasks +- **docs**: MkDocs documentation (~56MB) + +**Tech Stack**: Node.js ≥20, Python 3.9-3.12, Yarn 4.12.0, TypeScript, React 19, GraphQL, ElasticSearch/OpenSearch, Redis, RabbitMQ, MinIO + +## Critical Build Requirements + +### Enable Corepack First +**CRITICAL**: Before any yarn commands, enable corepack to use Yarn 4.12.0: +```bash +corepack enable # One-time setup, downloads correct Yarn version +``` + +### ALWAYS Copy .yarnrc.yml First +**Before any `yarn` command**, copy `.yarnrc.yml` from `opencti-platform/` to the working directory: +```bash +cd opencti-platform/opencti-graphql # or opencti-front +cp ../.yarnrc.yml .yarnrc.yml +yarn install +``` +**Why**: The `.yarnrc.yml` enforces security policies (disables scripts, 4320-min package age gate) and pinned versions. Commands will fail or behave unexpectedly without it. + +### Backend (opencti-graphql) + +**Install & Build**: +```bash +cd opencti-platform/opencti-graphql +cp ../.yarnrc.yml .yarnrc.yml +yarn install # ~5 min first time +yarn install:python # Python deps: pip3 install -r src/python/requirements.txt +yarn build:prod # Production build (~3 min) +# OR: yarn build:dev # Dev build with schema (~2 min) +``` + +**Linting & Type Checking**: +```bash +yarn check-ts # TypeScript (~30s) +yarn lint # ESLint (~45s) +``` + +**Testing** (requires Docker): +```bash +yarn test:ci-unit # Unit tests (~2 min) +yarn test:ci-integration-sync # Integration tests (~10-15 min) +yarn test:ci-rules-and-others # Rules/TAXII tests (~8 min) +``` + +**Common Issues**: +- **Missing Python deps**: Run `yarn install:python` after `yarn install` +- **Build timeout**: Use `NODE_OPTIONS=--max_old_space_size=8192` +- **Schema errors**: Run `yarn build:schema` +- **"Cannot find module opencti-manifest.json"**: Run `yarn get-connectors-manifest` + +### Frontend (opencti-front) + +**Install & Build**: +```bash +cd opencti-platform/opencti-front +cp ../.yarnrc.yml .yarnrc.yml +yarn install # ~4 min +yarn relay # Generate Relay artifacts (~1 min) +yarn build # Production build (~5 min) +``` + +**Linting & Type Checking**: +```bash +yarn check-ts # TypeScript (~40s) +yarn lint # ESLint (~30s) +``` + +**Testing**: +```bash +yarn test # Unit tests with Vitest (~2 min) +yarn test:coverage # With coverage (~3 min) +yarn test:e2e # E2E Playwright tests (~10 min, needs full stack) +node script/verify-translation.js # Translation validation (runs in CI) +``` + +### Client Python (pycti) + +**Setup & Test**: +```bash +cd client-python +pip3 install -r requirements.txt +pip3 install -r test-requirements.txt +pip3 install -e .[dev,doc] # Editable install + +# Linting: flake8 . (ignore E,W), black ., isort . +# Testing: python3 -m pytest --cov=pycti --no-header -vv (requires OpenCTI instance) +``` + +### Worker + +**Setup**: +```bash +cd opencti-worker +pip3 install -r src/requirements.txt +# Requires: OPENCTI_URL and OPENCTI_TOKEN environment variables +python3 src/worker.py # Start worker +``` + +## CI/CD Workflows + +**Main CI**: Docker build (~10 min), API tests (~20 min), Frontend tests (~5-15 min), Client Python matrix (3.9-3.12), License check. **All commits MUST be GPG signed**. + +## Local Development + +**Prerequisites**: Node.js ≥20, Python 3.9-3.12, Docker, `corepack enable`, `sudo sysctl -w vm.max_map_count=262144` + +**Start**: `cd opencti-platform/opencti-dev && docker compose up -d` (Elasticsearch, Redis, RabbitMQ, MinIO, Kibana) + +**Backend**: `cd opencti-platform/opencti-graphql`, copy `.yarnrc.yml`, edit `config/development.json`, run `yarn install && yarn install:python && yarn start` + +**Frontend**: `cd opencti-platform/opencti-front`, copy `.yarnrc.yml`, run `yarn install && yarn start` → http://localhost:3000 + +## Project Structure + +``` +opencti/ +├── opencti-platform/ +│ ├── .yarnrc.yml # MUST copy to subdirs +│ ├── opencti-graphql/ # Backend: src/, config/, tests/, vitest.config.*.ts, eslint.config.mjs +│ ├── opencti-front/ # Frontend: src/, tests_e2e/, lang/, relay.config.json +│ └── opencti-dev/docker-compose.yml +├── client-python/ # pycti/, tests/, pyproject.toml, .flake8, .isort.cfg +├── opencti-worker/src/ +└── scripts/ci/ # docker-compose.yml, ci-common.env +``` + +**Key Configs**: `eslint.config.mjs` (v9), `tsconfig.json` (strict), `vitest.config.*`, `.flake8`, `.isort.cfg`, `pyproject.toml` + +## Common Pitfalls & Solutions + +1. **Yarn command fails**: Missing `.yarnrc.yml` - ALWAYS copy it first +2. **"Module not found" errors**: Run `yarn install:python` for backend, check Relay artifacts for frontend +3. **Test failures in CI**: Local tests pass but CI fails → Check Docker service health, increase timeouts +4. **Build out of memory**: Add `NODE_OPTIONS=--max_old_space_size=8192` before yarn commands +5. **ElasticSearch won't start**: Run `sudo sysctl -w vm.max_map_count=262144` +6. **Unsigned commits rejected**: Configure GPG signing: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits + +## Code Review & Security + +**Pre-commit checks**: Lint (`yarn lint`, `black . && isort . && flake8 .`), types (`yarn check-ts`), tests (`yarn test:ci-unit`, `pytest`). **CodeQL** auto-runs on PRs. + +## Commit Message Format + +**Required**: `[component] Message (#issuenumber)` + +Components: `backend`, `frontend`, `client-python`, `worker`, `docs`, `tools`, `CI` + +Example: `[backend] Fix authentication error handling (#1234)` + +## Trust These Instructions + +These instructions are comprehensive and tested. Only search for additional information if you encounter a specific error not covered here or need details on a feature not mentioned. diff --git a/.github/instructions/code-review.instructions.md b/.github/instructions/code-review.instructions.md new file mode 100644 index 000000000000..a4e496859d94 --- /dev/null +++ b/.github/instructions/code-review.instructions.md @@ -0,0 +1,46 @@ +--- +applyTo: "**/*" +--- + +When reviewing code, focus on: + +## Security Critical Issues +- Check for hardcoded secrets, API keys, or credentials +- Look for SQL injection and XSS vulnerabilities +- Verify proper input validation and sanitization +- Review authentication and authorization logic + +## Performance Red Flags +- Identify N+1 database query problems +- Spot inefficient loops and algorithmic issues +- Check for memory leaks and resource cleanup +- Review caching opportunities for expensive operations + +## Code Quality Essentials +- Functions should be focused and appropriately sized +- Use clear, descriptive naming conventions +- Ensure proper error handling throughout + +## Review Style +- Be specific and actionable in feedback +- Explain the "why" behind recommendations +- Acknowledge good patterns when you see them +- Ask clarifying questions when code intent is unclear + +Always prioritize security vulnerabilities and performance issues that could impact users. + +Always suggest changes to improve readability. For example, this suggestion seeks to make the code more readable and also makes the validation logic reusable and testable. + +// Instead of: +if (user.email && user.email.includes('@') && user.email.length > 5) { + submitButton.enabled = true; +} else { + submitButton.enabled = false; +} + +// Consider: +function isValidEmail(email) { + return email && email.includes('@') && email.length > 5; +} + +submitButton.enabled = isValidEmail(user.email); diff --git a/.github/workflows/ci-docker-build.yml b/.github/workflows/ci-docker-build.yml index bc9a2ac4a4ca..36c62ec50869 100644 --- a/.github/workflows/ci-docker-build.yml +++ b/.github/workflows/ci-docker-build.yml @@ -83,7 +83,7 @@ jobs: - name: Upload opencti docker image artifact if: ${{ inputs.publish_to_registry == 'false' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: docker-image-opencti-platform path: /tmp/opencti-platform.tar @@ -135,7 +135,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: docker-image-opencti-worker path: /tmp/opencti-worker.tar diff --git a/.github/workflows/ci-test-api.yml b/.github/workflows/ci-test-api.yml index d1544a0f9bd8..ba10bae5b791 100644 --- a/.github/workflows/ci-test-api.yml +++ b/.github/workflows/ci-test-api.yml @@ -28,13 +28,13 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp - name: Download image worker - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-worker path: /tmp @@ -87,7 +87,7 @@ jobs: ' - name: Upload backend test result - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: @@ -150,13 +150,13 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp - name: Download image worker - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-worker path: /tmp @@ -225,7 +225,7 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp diff --git a/.github/workflows/ci-test-client-python.yml b/.github/workflows/ci-test-client-python.yml index f591da03df4b..bfc7dc882c44 100644 --- a/.github/workflows/ci-test-client-python.yml +++ b/.github/workflows/ci-test-client-python.yml @@ -39,7 +39,7 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp diff --git a/.github/workflows/ci-test-frontend.yml b/.github/workflows/ci-test-frontend.yml index 6534f613b4b7..747811443417 100644 --- a/.github/workflows/ci-test-frontend.yml +++ b/.github/workflows/ci-test-frontend.yml @@ -163,7 +163,7 @@ jobs: ' - name: Upload front e2e test results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: diff --git a/.gitignore b/.gitignore index 814ddeb4e11d..066dd4b3bd33 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ node_modules/ .venv venv /licenses/ +opencti-platform/opencti-front/.yarnrc.yml diff --git a/client-python/.readthedocs.yml b/.readthedocs.yaml similarity index 50% rename from client-python/.readthedocs.yml rename to .readthedocs.yaml index 5b8a44a7a92a..6bd91c3cbf7e 100644 --- a/client-python/.readthedocs.yml +++ b/.readthedocs.yaml @@ -1,6 +1,5 @@ ---- -# .readthedocs.yml -# Read the Docs configuration file +# .readthedocs.yaml +# Read the Docs configuration file for the Python client documentation # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required @@ -10,14 +9,12 @@ build: os: ubuntu-22.04 tools: python: "3.12" + apt_packages: + - graphviz -# Build documentation in the docs/ directory with Sphinx +# Build documentation in the client-python/docs/ directory with Sphinx sphinx: - configuration: docs/conf.py - -# Build documentation with MkDocs -#mkdocs: -# configuration: mkdocs.yml + configuration: client-python/docs/conf.py # Optionally build your docs in additional formats such as PDF and ePub formats: all @@ -25,5 +22,6 @@ formats: all # Optionally set the version of Python and requirements required to build your docs python: install: - - requirements: requirements.txt - - requirements: docs/requirements.txt + - requirements: client-python/requirements.txt + - requirements: client-python/docs/requirements.txt + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97155dd733c5..76ff975cfda3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,10 +49,13 @@ For general suggestions or questions about the project or the documentation, you * If you are interested in contributing to developing OpenCTI, please refer to the [detailed documentation](https://docs.opencti.io/latest/). It can be either a to fix an issue which is meaningful to you, or to develop a feature requested by others. * All commits messages must be formatted as: `[component] Message (#issuenumber)` where component should be: - * api + * backend * frontend + * client-python * worker - * doc + * docs + * tools + * CI * All commit must be signed, if you need to configure your git environement please see [Github documentation on signed commit][https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits] diff --git a/client-python/README.md b/client-python/README.md index de02fd6d3bc5..005376f17ccd 100644 --- a/client-python/README.md +++ b/client-python/README.md @@ -1,7 +1,7 @@ # OpenCTI client for Python [![Website](https://img.shields.io/badge/website-opencti.io-blue.svg)](https://opencti.io) -[![readthedocs](https://readthedocs.org/projects/opencti-client-for-python/badge/?style=flat)](https://opencti-client-for-python.readthedocs.io/en/latest/) +[![readthedocs](https://readthedocs.org/projects/opencti-python-client/badge/?style=flat)](https://opencti-python-client.readthedocs.io/en/latest/) [![Number of PyPI downloads](https://img.shields.io/pypi/dm/pycti.svg)](https://pypi.python.org/pypi/pycti/) [![Slack Status](https://img.shields.io/badge/slack-3K%2B%20members-4A154B)](https://community.filigran.io) @@ -53,11 +53,11 @@ $ pip install -e . ### Client usage -To learn about how to use the OpenCTI Python client and read some examples and cases, refer to [the client documentation](https://opencti-client-for-python.readthedocs.io/en/latest/client_usage/getting_started.html). +To learn about how to use the OpenCTI Python client and read some examples and cases, refer to [the client documentation](https://opencti-python-client.readthedocs.io/en/latest/client_usage/getting_started.html). ### API reference -To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://opencti-client-for-python.readthedocs.io/en/latest/pycti/pycti.html). +To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://opencti-python-client.readthedocs.io/en/latest/pycti/pycti.html). ## Tests diff --git a/client-python/docs/conf.py b/client-python/docs/conf.py index 7e3af910b83c..8341fb728302 100644 --- a/client-python/docs/conf.py +++ b/client-python/docs/conf.py @@ -11,23 +11,39 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os +import re import sys -sys.path.insert(0, os.path.abspath("..")) +# Add the client-python directory to the path for pycti module discovery +# This works both when building from client-python/ locally and from repo root via ReadTheDocs +docs_dir = os.path.dirname(os.path.abspath(__file__)) +client_python_dir = os.path.dirname(docs_dir) +sys.path.insert(0, client_python_dir) # -- Project information ----------------------------------------------------- -project = "OpenCTI client for Python" +project = "OpenCTI Python Client" copyright = "2025, Filigran" author = "OpenCTI Project" + # The full version, including alpha/beta/rc tags -release = "6.0.7" +# Read version from pycti/__init__.py without importing (avoids dependency issues) +def get_version(): + init_path = os.path.join(client_python_dir, "pycti", "__init__.py") + with open(init_path, "r") as f: + content = f.read() + match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE) + if match: + return match.group(1) + return "unknown" -master_doc = "index" -autoapi_modules = {"pycti": {"prune": True}} +release = get_version() +version = release + +master_doc = "index" pygments_style = "sphinx" @@ -38,11 +54,81 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.graphviz", "sphinx.ext.inheritance_diagram", - "autoapi.sphinx", + "autoapi.extension", "sphinx_autodoc_typehints", ] +# Graphviz configuration +graphviz_output_format = "svg" +inheritance_graph_attrs = { + "rankdir": "TB", + "size": '"6.0, 8.0"', +} +inheritance_node_attrs = { + "shape": "box", + "fontsize": 10, + "height": 0.25, + "style": '"setlinewidth(0.5),filled"', + "fillcolor": "white", +} + +# Napoleon settings for Google/NumPy style docstrings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_type_aliases = None + +# AutoAPI configuration for sphinx-autoapi +autoapi_type = "python" +autoapi_dirs = [os.path.join(client_python_dir, "pycti")] +autoapi_options = [ + "members", + "undoc-members", + "show-inheritance", + "show-module-summary", + "special-members", +] +autoapi_python_class_content = ( + "both" # Include both class docstring and __init__ docstring +) +autoapi_member_order = "bysource" +autoapi_keep_files = False +autoapi_add_toctree_entry = True +# Ignore top-level __init__.py to prevent "Undocumented" for re-exported classes +# Classes will be documented in their original modules with proper docstrings +autoapi_ignore = ["*/pycti/__init__.py"] + +# Mock imports for modules that can't be installed on ReadTheDocs +autodoc_mock_imports = [ + "magic", + "pika", + "stix2", + "pydantic", + "yaml", + "requests", + "cachetools", + "prometheus_client", + "opentelemetry", + "deprecation", + "fastapi", + "uvicorn", + "sseclient", + "datefinder", + "python_json_logger", +] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/client-python/docs/pycti/pycti.rst b/client-python/docs/pycti/pycti.rst index 0a7a9ea49104..c04bd070d4e3 100644 --- a/client-python/docs/pycti/pycti.rst +++ b/client-python/docs/pycti/pycti.rst @@ -24,52 +24,52 @@ Classes ======= - :py:class:`AttackPattern`: - Undocumented. + Main AttackPattern class for OpenCTI - :py:class:`Campaign`: - Undocumented. + Main Campaign class for OpenCTI - :py:class:`CaseIncident`: - Undocumented. + Main CaseIncident class for OpenCTI - :py:class:`CaseRfi`: - Undocumented. + Main CaseRfi class for OpenCTI - :py:class:`CaseRft`: - Undocumented. + Main CaseRft class for OpenCTI - :py:class:`Channel`: - Undocumented. + Main Channel class for OpenCTI - :py:class:`Task`: - Undocumented. + Main Task class for OpenCTI - :py:class:`ConnectorType`: An enumeration. - :py:class:`CourseOfAction`: - Undocumented. + Main CourseOfAction class for OpenCTI - :py:class:`DataComponent`: - Undocumented. + Main DataComponent class for OpenCTI - :py:class:`DataSource`: - Undocumented. + Main DataSource class for OpenCTI - :py:class:`ExternalReference`: - Undocumented. + Main ExternalReference class for OpenCTI - :py:class:`Feedback`: - Undocumented. + Main Feedback class for OpenCTI - :py:class:`Grouping`: - Undocumented. + Main Grouping class for OpenCTI - :py:class:`Identity`: - Undocumented. + Main Identity class for OpenCTI - :py:class:`Incident`: - Undocumented. + Main Incident class for OpenCTI - :py:class:`Indicator`: Main Indicator class for OpenCTI @@ -78,40 +78,40 @@ Classes Main Infrastructure class for OpenCTI - :py:class:`IntrusionSet`: - Undocumented. + Main IntrusionSet class for OpenCTI - :py:class:`KillChainPhase`: - Undocumented. + Main KillChainPhase class for OpenCTI - :py:class:`Label`: - Undocumented. + Main Label class for OpenCTI - :py:class:`Location`: - Undocumented. + Main Location class for OpenCTI - :py:class:`Malware`: - Undocumented. + Main Malware class for OpenCTI - :py:class:`MalwareAnalysis`: - Undocumented. + Main MalwareAnalysis class for OpenCTI - :py:class:`MarkingDefinition`: - Undocumented. + Main MarkingDefinition class for OpenCTI - :py:class:`Note`: - Undocumented. + Main Note class for OpenCTI - :py:class:`ObservedData`: - Undocumented. + Main ObservedData class for OpenCTI - :py:class:`OpenCTIApiClient`: Main API client for OpenCTI - :py:class:`OpenCTIApiConnector`: - OpenCTIApiConnector + OpenCTI API Connector client - :py:class:`OpenCTIApiWork`: - OpenCTIApiJob + OpenCTI API Work client - :py:class:`OpenCTIConnector`: Main class for OpenCTI connector @@ -120,40 +120,40 @@ Classes Python API for OpenCTI connector - :py:class:`OpenCTIMetricHandler`: - Undocumented. + Main OpenCTI Metric Handler class - :py:class:`OpenCTIStix2`: Python API for Stix2 in OpenCTI - :py:class:`OpenCTIStix2Splitter`: - Undocumented. + Main OpenCTI Stix2 Splitter class - :py:class:`OpenCTIStix2Update`: Python API for Stix2 Update in OpenCTI - :py:class:`OpenCTIStix2Utils`: - Undocumented. + Main OpenCTI Stix2 Utils class - :py:class:`Opinion`: - Undocumented. + Main Opinion class for OpenCTI - :py:class:`Report`: - Undocumented. + Main Report class for OpenCTI - :py:class:`StixCoreRelationship`: - Undocumented. + Main StixCoreRelationship class for OpenCTI - :py:class:`StixCyberObservable`: - deprecated [>=6.2 & <6.5]` + Deprecated StixCyberObservable class [>=6.2 & <6.5] - :py:class:`StixNestedRefRelationship`: - Undocumented. + Main StixNestedRefRelationship class for OpenCTI - :py:class:`StixCyberObservableTypes`: An enumeration. - :py:class:`StixDomainObject`: - Undocumented. + Main StixDomainObject class for OpenCTI - :py:class:`StixMetaTypes`: An enumeration. @@ -162,10 +162,10 @@ Classes An enumeration. - :py:class:`StixObjectOrStixRelationship`: - Undocumented. + Main StixObjectOrStixRelationship class for OpenCTI - :py:class:`StixSightingRelationship`: - Undocumented. + Main StixSightingRelationship class for OpenCTI - :py:class:`ThreatActor`: Main ThreatActor class for OpenCTI @@ -177,10 +177,10 @@ Classes Main ThreatActorIndividual class for OpenCTI - :py:class:`Tool`: - Undocumented. + Main Tool class for OpenCTI - :py:class:`Vulnerability`: - Undocumented. + Main Vulnerability class for OpenCTI - :py:class:`CustomObjectCaseIncident`: Case-Incident object. diff --git a/client-python/docs/requirements.txt b/client-python/docs/requirements.txt index 31356520e3cf..8186f6ff9994 100644 --- a/client-python/docs/requirements.txt +++ b/client-python/docs/requirements.txt @@ -1,4 +1,4 @@ -autoapi==2.0.1 -sphinx==9.0.4 -sphinx-autodoc-typehints==3.2.0 -sphinx_rtd_theme==3.0.2 +sphinx-autoapi>=3.0.0 +sphinx>=8.2,<9 +sphinx-autodoc-typehints>=3.0.0 +sphinx_rtd_theme>=3.0.0 diff --git a/client-python/pycti/__init__.py b/client-python/pycti/__init__.py index ffde85595899..febd88b7290c 100644 --- a/client-python/pycti/__init__.py +++ b/client-python/pycti/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "6.9.3" +__version__ = "6.9.9" from .api.opencti_api_client import OpenCTIApiClient from .api.opencti_api_connector import OpenCTIApiConnector @@ -12,8 +12,6 @@ from .connector.opencti_metric_handler import OpenCTIMetricHandler from .entities.opencti_attack_pattern import AttackPattern from .entities.opencti_campaign import Campaign - -# Administrative entities from .entities.opencti_capability import Capability from .entities.opencti_case_incident import CaseIncident from .entities.opencti_case_rfi import CaseRfi @@ -22,6 +20,7 @@ from .entities.opencti_course_of_action import CourseOfAction from .entities.opencti_data_component import DataComponent from .entities.opencti_data_source import DataSource +from .entities.opencti_event import Event from .entities.opencti_external_reference import ExternalReference from .entities.opencti_feedback import Feedback from .entities.opencti_group import Group @@ -33,16 +32,21 @@ from .entities.opencti_intrusion_set import IntrusionSet from .entities.opencti_kill_chain_phase import KillChainPhase from .entities.opencti_label import Label +from .entities.opencti_language import Language from .entities.opencti_location import Location from .entities.opencti_malware import Malware from .entities.opencti_malware_analysis import MalwareAnalysis from .entities.opencti_marking_definition import MarkingDefinition +from .entities.opencti_narrative import Narrative from .entities.opencti_note import Note from .entities.opencti_observed_data import ObservedData from .entities.opencti_opinion import Opinion from .entities.opencti_report import Report from .entities.opencti_role import Role +from .entities.opencti_security_coverage import SecurityCoverage from .entities.opencti_settings import Settings +from .entities.opencti_stix import Stix +from .entities.opencti_stix_core_object import StixCoreObject from .entities.opencti_stix_core_relationship import StixCoreRelationship from .entities.opencti_stix_cyber_observable import StixCyberObservable from .entities.opencti_stix_domain_object import StixDomainObject @@ -57,6 +61,7 @@ from .entities.opencti_threat_actor_individual import ThreatActorIndividual from .entities.opencti_tool import Tool from .entities.opencti_user import User +from .entities.opencti_vocabulary import Vocabulary from .entities.opencti_vulnerability import Vulnerability from .utils.constants import ( CustomObjectCaseIncident, @@ -86,9 +91,16 @@ ) from .utils.opencti_stix2_splitter import OpenCTIStix2Splitter from .utils.opencti_stix2_update import OpenCTIStix2Update -from .utils.opencti_stix2_utils import OpenCTIStix2Utils +from .utils.opencti_stix2_utils import ( + ALIASES_FIELD, + X_OPENCTI_ALIASES_FIELD, + OpenCTIStix2Utils, + is_stix_object_aliased, + resolve_aliases_field, +) __all__ = [ + "ALIASES_FIELD", "AttackPattern", "Campaign", "CaseIncident", @@ -100,6 +112,7 @@ "CourseOfAction", "DataComponent", "DataSource", + "Event", "ExternalReference", "Feedback", "Grouping", @@ -110,10 +123,12 @@ "IntrusionSet", "KillChainPhase", "Label", + "Language", "Location", "Malware", "MalwareAnalysis", "MarkingDefinition", + "Narrative", "Note", "ObservedData", "OpenCTIApiClient", @@ -128,6 +143,9 @@ "OpenCTIStix2Utils", "Opinion", "Report", + "SecurityCoverage", + "Stix", + "StixCoreObject", "StixCoreRelationship", "StixCyberObservable", "StixNestedRefRelationship", @@ -141,12 +159,15 @@ "ThreatActorGroup", "ThreatActorIndividual", "Tool", + "Vocabulary", "Vulnerability", + "X_OPENCTI_ALIASES_FIELD", "get_config_variable", + "is_stix_object_aliased", + "resolve_aliases_field", "CustomObjectCaseIncident", "CustomObjectTask", "CustomObjectChannel", - "StixCyberObservableTypes", "CustomObservableCredential", "CustomObservableHostname", "CustomObservableUserAgent", diff --git a/client-python/pycti/api/opencti_api_client.py b/client-python/pycti/api/opencti_api_client.py index 76309d8e9ede..29cff1c72c7e 100644 --- a/client-python/pycti/api/opencti_api_client.py +++ b/client-python/pycti/api/opencti_api_client.py @@ -10,7 +10,7 @@ import signal import tempfile import threading -from typing import Dict, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import magic import requests @@ -93,6 +93,19 @@ def build_request_headers(token: str, custom_headers: str, app_logger, provider: str): + """Build request headers for OpenCTI API requests. + + :param token: the API authentication token + :type token: str + :param custom_headers: custom headers in format "header01:value;header02:value" + :type custom_headers: str + :param app_logger: the application logger instance + :type app_logger: logging.Logger + :param provider: the provider string for User-Agent header + :type provider: str + :return: dictionary of request headers + :rtype: dict + """ pycti_user_agent = "pycti/" + __version__ if provider is not None: pycti_user_agent += " " + provider @@ -115,7 +128,28 @@ def build_request_headers(token: str, custom_headers: str, app_logger, provider: class File: + """File object for OpenCTI file uploads. + + Represents a file to be uploaded via the OpenCTI API. + + :param name: the filename + :type name: str + :param data: the file content (string or bytes) + :type data: str or bytes + :param mime: the MIME type of the file, defaults to "text/plain" + :type mime: str, optional + """ + def __init__(self, name, data, mime="text/plain"): + """Initialize the File instance. + + :param name: the filename + :type name: str + :param data: the file content + :type data: str or bytes + :param mime: the MIME type of the file (default: "text/plain") + :type mime: str + """ self.name = name self.data = data self.mime = mime @@ -132,14 +166,8 @@ class OpenCTIApiClient: :type log_level: str, optional :param ssl_verify: Requiring the requests to verify the TLS certificate at the server. :type ssl_verify: bool, str, optional - :param proxies: - :type proxies: dict, optional, The proxy configuration, would have `http` and `https` attributes. Defaults to {} - ``` - proxies: { - "http": "http://my_proxy:8080" - "https": "http://my_proxy:8080" - } - ``` + :param proxies: proxy configuration with "http" and "https" keys (e.g., {"http": "http://my_proxy:8080", "https": "http://my_proxy:8080"}) + :type proxies: dict, optional :param json_logging: format the logs as json if set to True :type json_logging: bool, optional :param bundle_send_to_queue: if bundle will be sent to queue @@ -166,12 +194,40 @@ def __init__( json_logging: bool = False, bundle_send_to_queue: bool = True, cert: Union[str, Tuple[str, str], None] = None, - custom_headers: str = None, + custom_headers: Optional[str] = None, perform_health_check: bool = True, requests_timeout: int = 300, - provider: str = None, + provider: Optional[str] = None, ): - """Constructor method""" + """Initialize the OpenCTIApiClient instance. + + :param url: OpenCTI platform URL + :type url: str + :param token: OpenCTI API authentication token + :type token: str + :param log_level: logging level (default: "info") + :type log_level: str + :param ssl_verify: SSL certificate verification setting + :type ssl_verify: Union[bool, str] + :param proxies: proxy configuration dictionary with "http" and "https" keys + :type proxies: Dict[str, str] or None + :param json_logging: whether to format logs as JSON (default: False) + :type json_logging: bool + :param bundle_send_to_queue: whether bundles are sent to queue (default: True) + :type bundle_send_to_queue: bool + :param cert: client certificate path or tuple of (cert, key) paths + :type cert: str, tuple, or None + :param custom_headers: custom headers in format "header01:value;header02:value" + :type custom_headers: str or None + :param perform_health_check: whether to check API access on init (default: True) + :type perform_health_check: bool + :param requests_timeout: timeout for API requests in seconds (default: 300) + :type requests_timeout: int + :param provider: client provider for User-Agent header (format: provider/version) + :type provider: str or None + + :raises ValueError: If URL or token is missing or invalid + """ # Check configuration self.bundle_send_to_queue = bundle_send_to_queue @@ -218,20 +274,21 @@ def __init__( self.stix2 = OpenCTIStix2(self) self.pir = OpenCTIApiPir(self) self.internal_file = OpenCTIApiInternalFile(self) + self.file = File # File class for creating upload objects # Define the entities self.vocabulary = Vocabulary(self) self.label = Label(self) self.marking_definition = MarkingDefinition(self) - self.external_reference = ExternalReference(self, File) + self.external_reference = ExternalReference(self) self.kill_chain_phase = KillChainPhase(self) self.opencti_stix_object_or_stix_relationship = StixObjectOrStixRelationship( self ) self.stix = Stix(self) - self.stix_domain_object = StixDomainObject(self, File) - self.stix_core_object = StixCoreObject(self, File) - self.stix_cyber_observable = StixCyberObservable(self, File) + self.stix_domain_object = StixDomainObject(self) + self.stix_core_object = StixCoreObject(self) + self.stix_cyber_observable = StixCyberObservable(self) self.stix_core_relationship = StixCoreRelationship(self) self.stix_sighting_relationship = StixSightingRelationship(self) self.stix_nested_ref_relationship = StixNestedRefRelationship(self) @@ -427,44 +484,135 @@ def _get_certificate_content(self, https_ca_certificates): ) def set_applicant_id_header(self, applicant_id): + """Set the applicant ID header for impersonation. + + :param applicant_id: the ID of the user to impersonate + :type applicant_id: str + """ self.request_headers["opencti-applicant-id"] = applicant_id def set_playbook_id_header(self, playbook_id): + """Set the playbook ID header for tracking playbook execution. + + :param playbook_id: the ID of the playbook being executed + :type playbook_id: str + """ self.request_headers["opencti-playbook-id"] = playbook_id def set_event_id(self, event_id): + """Set the event ID header for event tracking. + + :param event_id: the ID of the event + :type event_id: str + """ self.request_headers["opencti-event-id"] = event_id def get_draft_id(self): + """Get the current draft ID. + + :return: the current draft ID or empty string if not set + :rtype: str + """ if self.draft_id is None: return "" return self.draft_id def set_draft_id(self, draft_id): + """Set the draft ID header for draft mode operations. + + :param draft_id: the ID of the draft workspace + :type draft_id: str + """ self.draft_id = draft_id self.request_headers["opencti-draft-id"] = draft_id def set_synchronized_upsert_header(self, synchronized): + """Set the synchronized upsert header. + + :param synchronized: whether upsert should be synchronized + :type synchronized: bool + """ self.request_headers["synchronized-upsert"] = ( "true" if synchronized is True else "false" ) def set_previous_standard_header(self, previous_standard): + """Set the previous standard header for update operations. + + :param previous_standard: the previous standard ID + :type previous_standard: str + """ self.request_headers["previous-standard"] = previous_standard def get_request_headers(self, hide_token=True): + """Get a copy of current request headers. + + :param hide_token: if True, masks the Authorization token with asterisks + :type hide_token: bool + :return: copy of request headers + :rtype: dict + """ request_headers_copy = self.request_headers.copy() if hide_token and "Authorization" in request_headers_copy: request_headers_copy["Authorization"] = "*****" return request_headers_copy def set_retry_number(self, retry_number): + """Set the retry number header for tracking retries. + + :param retry_number: the current retry attempt number, or None to clear + :type retry_number: int or None + """ self.request_headers["opencti-retry-number"] = ( "" if retry_number is None else str(retry_number) ) + def _extract_files(self, obj, path_prefix=""): + """Recursively extract File objects from nested dictionaries. + + :param obj: the object to search for File objects + :type obj: any + :param path_prefix: the current path prefix for nested keys + :type path_prefix: str + :return: tuple of (cleaned_obj, files_vars) where cleaned_obj has Files replaced with None + :rtype: tuple + """ + if isinstance(obj, File): + return None, [{"key": path_prefix, "file": obj, "multiple": False}] + + if ( + isinstance(obj, list) + and len(obj) > 0 + and all(map(lambda x: isinstance(x, File), obj)) + ): + return [None] * len(obj), [ + {"key": path_prefix, "file": obj, "multiple": True} + ] + + if isinstance(obj, dict): + cleaned = {} + files_vars = [] + for key, val in obj.items(): + new_path = f"{path_prefix}.{key}" if path_prefix else key + cleaned_val, nested_files = self._extract_files(val, new_path) + cleaned[key] = cleaned_val + files_vars.extend(nested_files) + return cleaned, files_vars + + if isinstance(obj, list): + cleaned = [] + files_vars = [] + for i, item in enumerate(obj): + new_path = f"{path_prefix}.{i}" if path_prefix else str(i) + cleaned_item, nested_files = self._extract_files(item, new_path) + cleaned.append(cleaned_item) + files_vars.extend(nested_files) + return cleaned, files_vars + + return obj, [] + def query(self, query, variables=None, disable_impersonate=False): - """submit a query to the OpenCTI GraphQL API + """Submit a query to the OpenCTI GraphQL API. :param query: GraphQL query string :type query: str @@ -472,29 +620,16 @@ def query(self, query, variables=None, disable_impersonate=False): :type variables: dict, optional :param disable_impersonate: removes impersonate header if set to True, defaults to False :type disable_impersonate: bool, optional - :return: returns the response json content - :rtype: Any + :return: returns the response JSON content + :rtype: dict + :raises ValueError: if the API returns an error or non-200 status code """ variables = variables or {} - query_var = {} - files_vars = [] # Implementation of spec https://github.com/jaydenseric/graphql-multipart-request-spec # Support for single or multiple upload # Batching or mixed upload or not supported - var_keys = variables.keys() - for key in var_keys: - val = variables[key] - is_file = type(val) is File - is_files = ( - isinstance(val, list) - and len(val) > 0 - and all(map(lambda x: isinstance(x, File), val)) - ) - if is_file or is_files: - files_vars.append({"key": key, "file": val, "multiple": is_files}) - query_var[key] = None if is_file else [None] * len(val) - else: - query_var[key] = val + # Recursively extract File objects from nested dictionaries + query_var, files_vars = self._extract_files(variables) query_headers = self.request_headers.copy() if disable_impersonate and "opencti-applicant-id" in query_headers: @@ -608,36 +743,50 @@ def query(self, query, variables=None, disable_impersonate=False): raise ValueError(r.text) def fetch_opencti_file(self, fetch_uri, binary=False, serialize=False): - """get file from the OpenCTI API + """Get file from the OpenCTI API. :param fetch_uri: download URI to use :type fetch_uri: str - :param binary: [description], defaults to False + :param binary: if True, returns raw bytes; if False, returns text, defaults to False :type binary: bool, optional - :return: returns either the file content as text or bytes based on `binary` - :rtype: str or bytes + :param serialize: if True, returns base64-encoded content, defaults to False + :type serialize: bool, optional + :return: returns either the file content as text, bytes, base64-encoded string, or None on failure + :rtype: str, bytes, or None """ - - r = self.session.get( - fetch_uri, - headers=self.request_headers, - verify=self.ssl_verify, - cert=self.cert, - proxies=self.proxies, - timeout=self.session_requests_timeout, - ) - if binary: + try: + r = self.session.get( + fetch_uri, + headers=self.request_headers, + verify=self.ssl_verify, + cert=self.cert, + proxies=self.proxies, + timeout=self.session_requests_timeout, + ) + # Check if request was successful + if not r.ok: + self.app_logger.warning( + "Failed to fetch file", + {"uri": fetch_uri, "status_code": r.status_code}, + ) + return None + if binary: + if serialize: + return base64.b64encode(r.content).decode("utf-8") + return r.content if serialize: - return base64.b64encode(r.content).decode("utf-8") - return r.content - if serialize: - return base64.b64encode(r.text).decode("utf-8") - return r.text + return base64.b64encode(r.text.encode("utf-8")).decode("utf-8") + return r.text + except Exception as e: + self.app_logger.warning( + "Error fetching file", {"uri": fetch_uri, "error": str(e)} + ) + return None def health_check(self): - """submit an example request to the OpenCTI API. + """Submit an example request to the OpenCTI API. - :return: returns `True` if the health check has been successful + :return: returns True if the health check has been successful :rtype: bool """ try: @@ -659,10 +808,10 @@ def health_check(self): return False def get_logs_worker_config(self): - """get the logsWorkerConfig + """Get the logs worker configuration from the OpenCTI platform. - return: the logsWorkerConfig - rtype: dict + :return: the logs worker configuration including Elasticsearch settings + :rtype: dict """ self.app_logger.info("Getting logs worker config...") @@ -683,11 +832,11 @@ def get_logs_worker_config(self): return result["data"]["logsWorkerConfig"] def not_empty(self, value): - """check if a value is empty for str, list and int + """Check if a value is empty for str, list and int. :param value: value to check :type value: str or list or int or float or bool or datetime.date - :return: returns `True` if the value is one of the supported types and not empty + :return: returns True if the value is one of the supported types and not empty :rtype: bool """ @@ -697,10 +846,7 @@ def not_empty(self, value): if isinstance(value, datetime.date): return True if isinstance(value, str): - if len(value) > 0: - return True - else: - return False + return len(value) > 0 if isinstance(value, dict): return bool(value) if isinstance(value, list): @@ -713,17 +859,18 @@ def not_empty(self, value): return True if isinstance(value, int): return True - else: - return False - else: return False + return False def process_multiple(self, data: dict, with_pagination=False) -> Union[dict, list]: - """processes data returned by the OpenCTI API with multiple entities + """Process data returned by the OpenCTI API with multiple entities. :param data: data to process - :param with_pagination: whether to use pagination with the API - :returns: returns either a dict or list with the processes entities + :type data: dict + :param with_pagination: whether to use pagination with the API, defaults to False + :type with_pagination: bool, optional + :return: returns either a dict or list with the processed entities + :rtype: dict or list """ if with_pagination: @@ -743,7 +890,7 @@ def process_multiple(self, data: dict, with_pagination=False) -> Union[dict, lis result.append(self.process_multiple_fields(row)) return result - # -- When data is wrapper in edges + # -- When data is wrapped in edges for edge in ( data["edges"] if "edges" in data and data["edges"] is not None else [] ): @@ -759,10 +906,12 @@ def process_multiple(self, data: dict, with_pagination=False) -> Union[dict, lis return result def process_multiple_ids(self, data) -> list: - """processes data returned by the OpenCTI API with multiple ids + """Process data returned by the OpenCTI API with multiple ids. :param data: data to process + :type data: list :return: returns a list of ids + :rtype: list """ result = [] @@ -775,7 +924,7 @@ def process_multiple_ids(self, data) -> list: return result def process_multiple_fields(self, data): - """processes data returned by the OpenCTI API with multiple fields + """Process data returned by the OpenCTI API with multiple fields. :param data: data to process :type data: dict @@ -891,12 +1040,13 @@ def upload_file(self, **kwargs): } """ if data is None: - data = open(file_name, "rb") + with open(file_name, "rb") as f: + data = f.read() if file_name.endswith(".json"): mime_type = "application/json" else: mime_type = magic.from_file(file_name, mime=True) - query_vars = {"file": (File(file_name, data, mime_type))} + query_vars = {"file": File(file_name, data, mime_type)} # optional file markings if file_markings is not None: query_vars["fileMarkings"] = file_markings @@ -906,10 +1056,14 @@ def upload_file(self, **kwargs): return None def create_draft(self, **kwargs): - """create a draft in OpenCTI API - :param `**kwargs`: arguments for file name creating draft (required: `draft_name`) - :return: returns the query response for the draft creation - :rtype: id + """Create a draft in OpenCTI API. + + :param draft_name: the name of the draft to create (required) + :type draft_name: str + :param entity_id: the entity ID to associate with the draft + :type entity_id: str, optional + :return: returns the draft workspace ID + :rtype: str """ draft_name = kwargs.get("draft_name", None) @@ -934,9 +1088,18 @@ def create_draft(self, **kwargs): return None def upload_pending_file(self, **kwargs): - """upload a file to OpenCTI API - - :param `**kwargs`: arguments for file upload (required: `file_name` and `data`) + """Upload a pending file to OpenCTI API. + + :param file_name: the name of the file to upload (required) + :type file_name: str + :param data: the file content, defaults to reading from file_name path + :type data: str or bytes, optional + :param mime_type: the MIME type of the file, defaults to "text/plain" + :type mime_type: str, optional + :param entity_id: the entity ID to associate with the file + :type entity_id: str, optional + :param file_markings: list of marking definition IDs to apply + :type file_markings: list, optional :return: returns the query response for the file upload :rtype: dict """ @@ -958,7 +1121,8 @@ def upload_pending_file(self, **kwargs): } """ if data is None: - data = open(file_name, "rb") + with open(file_name, "rb") as f: + data = f.read() if file_name.endswith(".json"): mime_type = "application/json" else: @@ -966,7 +1130,7 @@ def upload_pending_file(self, **kwargs): return self.query( query, { - "file": (File(file_name, data, mime_type)), + "file": File(file_name, data, mime_type), "entityId": entity_id, "file_markings": file_markings, }, @@ -976,9 +1140,14 @@ def upload_pending_file(self, **kwargs): return None def send_bundle_to_api(self, **kwargs): - """Push a bundle to a queue through OpenCTI API - - :param `**kwargs`: arguments for bundle push (required: `connectorId` and `bundle`) + """Push a bundle to a queue through OpenCTI API. + + :param connector_id: the connector ID (required) + :type connector_id: str + :param bundle: the STIX bundle to push (required) + :type bundle: str + :param work_id: the work ID to associate with the bundle + :type work_id: str, optional :return: returns the query response for the bundle push :rtype: dict """ @@ -1007,10 +1176,12 @@ def send_bundle_to_api(self, **kwargs): return None def get_stix_content(self, id): - """get the STIX content of any entity + """Get the STIX content of any entity. - return: the STIX content in JSON - rtype: dict + :param id: the ID of the entity + :type id: str + :return: the STIX content in JSON + :rtype: dict """ self.app_logger.info("Entity in JSON", {"id": id}) @@ -1023,47 +1194,68 @@ def get_stix_content(self, id): return json.loads(result["data"]["stix"]) @staticmethod - def get_attribute_in_extension(key, object) -> any: + def get_attribute_in_extension(key, stix_object) -> Any: + """Get an attribute value from OpenCTI STIX extensions. + + Searches for the key in OpenCTI extension definitions, or falls back + to the object's top-level attributes. + + :param key: the attribute key to retrieve + :type key: str + :param stix_object: the STIX object containing extensions + :type stix_object: dict + :return: the attribute value if found, None otherwise + :rtype: Any + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ][key] elif ( - "extensions" in object + "extensions" in stix_object and "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ][key] - elif key in object and key not in ["type"]: - return object[key] + elif key in stix_object and key not in ["type"]: + return stix_object[key] return None @staticmethod - def get_attribute_in_mitre_extension(key, object) -> any: + def get_attribute_in_mitre_extension(key, stix_object) -> Any: + """Get an attribute value from MITRE ATT&CK STIX extension. + + :param key: the attribute key to retrieve + :type key: str + :param stix_object: the STIX object containing extensions + :type stix_object: dict + :return: the attribute value if found, None otherwise + :rtype: Any + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ][key] return None diff --git a/client-python/pycti/api/opencti_api_connector.py b/client-python/pycti/api/opencti_api_connector.py index d65a23876764..93f9121cd7d3 100644 --- a/client-python/pycti/api/opencti_api_connector.py +++ b/client-python/pycti/api/opencti_api_connector.py @@ -1,22 +1,35 @@ import json -from typing import Any, Dict +from typing import Any, Dict, List from pycti.connector.opencti_connector import OpenCTIConnector class OpenCTIApiConnector: - """OpenCTIApiConnector""" + """OpenCTI Connector API class. + + Manages connector operations including registration, pinging, and listing. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiConnector instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def read(self, connector_id: str) -> Dict: - """Reading the connector and its details + """Read the connector and its details. + :param connector_id: the id of the connector + :type connector_id: str :return: return all the connector details :rtype: dict """ - self.api.app_logger.info("[INFO] Getting connector details ...") + self.api.app_logger.info("Getting connector details ...") query = """ query GetConnector($id: String!) { connector(id: $id) { @@ -32,27 +45,27 @@ def read(self, connector_id: str) -> Dict: messages_number messages_size } - updated_at - created_at - config { - listen - listen_exchange - push - push_exchange - push_routing - } - built_in + updated_at + created_at + config { + listen + listen_exchange + push + push_exchange + push_routing + } + built_in } } """ result = self.api.query(query, {"id": connector_id}) return result["data"]["connector"] - def list(self) -> Dict: - """list available connectors + def list(self) -> List[Dict]: + """List available connectors. - :return: return dict with connectors - :rtype: dict + :return: list of connector dictionaries + :rtype: list[dict] """ self.api.app_logger.info("Getting connectors ...") @@ -91,14 +104,14 @@ def list(self) -> Dict: def ping( self, connector_id: str, connector_state: Any, connector_info: Dict ) -> Dict: - """pings a connector by id and state + """Ping a connector by ID and state. - :param connector_id: the connectors id + :param connector_id: the connector id :type connector_id: str :param connector_state: state for the connector - :type connector_state: - :param connector_info: all details connector - :type connector_info: Dict + :type connector_state: Any + :param connector_info: all details about the connector + :type connector_info: dict :return: the response pingConnector data dict :rtype: dict """ @@ -115,7 +128,7 @@ def ping( queue_messages_size next_run_datetime last_run_datetime - } + } } } """ @@ -130,9 +143,9 @@ def ping( return result["data"]["pingConnector"] def register(self, connector: OpenCTIConnector) -> Dict: - """register a connector with OpenCTI + """Register a connector with OpenCTI. - :param connector: `OpenCTIConnector` connector object + :param connector: OpenCTIConnector connector object :type connector: OpenCTIConnector :return: the response registerConnector data dict :rtype: dict @@ -152,6 +165,15 @@ def register(self, connector: OpenCTIConnector) -> Dict: user pass } + s3 { + endpoint + port + use_ssl + bucket_name + bucket_region + access_key + secret_key + } listen listen_routing listen_exchange @@ -167,11 +189,11 @@ def register(self, connector: OpenCTIConnector) -> Dict: return result["data"]["registerConnector"] def unregister(self, _id: str) -> Dict: - """unregister a connector with OpenCTI + """Unregister a connector with OpenCTI. - :param _id: `OpenCTIConnector` connector id - :type _id: string - :return: the response registerConnector data dict + :param _id: the connector id to unregister + :type _id: str + :return: the response deleteConnector data dict :rtype: dict """ query = """ diff --git a/client-python/pycti/api/opencti_api_draft.py b/client-python/pycti/api/opencti_api_draft.py index 9869c96191de..72d38e9c9862 100644 --- a/client-python/pycti/api/opencti_api_draft.py +++ b/client-python/pycti/api/opencti_api_draft.py @@ -1,11 +1,34 @@ class OpenCTIApiDraft: - """OpenCTIApiDraft""" + """OpenCTI Draft API class. + + Manages draft workspace operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiDraft instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): - id = kwargs.get("id", None) + """Delete a draft workspace. + + :param id: the draft workspace id + :type id: str + :return: None + :rtype: None + """ + draft_id = kwargs.get("id", None) + if draft_id is None: + self.api.app_logger.error( + "[opencti_draft] Cannot delete draft workspace, missing parameter: id" + ) + return None query = """ mutation DraftWorkspaceDelete($id: ID!) { draftWorkspaceDelete(id: $id) @@ -14,6 +37,6 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": draft_id, }, ) diff --git a/client-python/pycti/api/opencti_api_internal_file.py b/client-python/pycti/api/opencti_api_internal_file.py index a49b040797ae..196e3f0d6b75 100644 --- a/client-python/pycti/api/opencti_api_internal_file.py +++ b/client-python/pycti/api/opencti_api_internal_file.py @@ -1,10 +1,28 @@ class OpenCTIApiInternalFile: - """OpenCTIApiInternalFile""" + """OpenCTI Internal File API class. + + Manages internal file operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiInternalFile instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): + """Delete an internal file. + + :param item: the item containing file information with id in extensions + :type item: dict + :return: None + :rtype: None + """ item = kwargs.get("item", None) file_name = self.api.get_attribute_in_extension("id", item) if file_name is not None: @@ -21,6 +39,6 @@ def delete(self, **kwargs): ) else: self.api.app_logger.error( - "[stix_internal_file] Cant delete internal file, missing parameters: fileName" + "[opencti_internal_file] Cannot delete internal file, missing parameters: fileName" ) return None diff --git a/client-python/pycti/api/opencti_api_notification.py b/client-python/pycti/api/opencti_api_notification.py index 6f49b280a3de..4956fbad082d 100644 --- a/client-python/pycti/api/opencti_api_notification.py +++ b/client-python/pycti/api/opencti_api_notification.py @@ -1,13 +1,31 @@ class OpenCTIApiNotification: - """OpenCTIApiJob""" + """OpenCTI Notification API class. + + Manages notification operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiNotification instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): + """Delete a notification. + + :param id: the notification id + :type id: str + :return: None + :rtype: None + """ notification_id = kwargs.get("id", None) self.api.app_logger.info( - "Deleting notifcation", {"notification_id": notification_id} + "Deleting notification", {"notification_id": notification_id} ) query = """ mutation notificationDelete($id: ID!) { @@ -17,16 +35,36 @@ def delete(self, **kwargs): self.api.query(query, {"id": notification_id}) def update_field(self, **kwargs): + """Update a notification field. + + :param id: the notification id + :type id: str + :param input: the input fields to update (list of key/value dicts) + :type input: list + :return: None + :rtype: None + """ notification_id = kwargs.get("id", None) - input = kwargs.get("input", None) - for input_value in input: + field_input = kwargs.get("input", None) + if field_input is None: + return None + for input_value in field_input: if input_value["key"] == "is_read": is_read_value = bool(input_value["value"][0]) self.mark_as_read(notification_id, is_read_value) def mark_as_read(self, notification_id: str, read: bool): + """Mark a notification as read or unread. + + :param notification_id: the notification id + :type notification_id: str + :param read: whether to mark as read (True) or unread (False) + :type read: bool + :return: None + :rtype: None + """ self.api.app_logger.info( - "Marking notifcation as read", + "Marking notification as read", {"notification_id": notification_id, "read": read}, ) query = """ diff --git a/client-python/pycti/api/opencti_api_pir.py b/client-python/pycti/api/opencti_api_pir.py index b3554e87ff41..ede7af1da12f 100644 --- a/client-python/pycti/api/opencti_api_pir.py +++ b/client-python/pycti/api/opencti_api_pir.py @@ -1,12 +1,32 @@ class OpenCTIApiPir: - """OpenCTIApiPir""" + """OpenCTI PIR (Priority Intelligence Requirements) API class. + + Manages PIR flagging operations on elements. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiPir instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def pir_flag_element(self, **kwargs): - id = kwargs.get("id", None) - input = kwargs.get("input", None) + """Flag an element with a PIR. + + :param id: the element id + :type id: str + :param input: the PIR flag input (PirFlagElementInput format) + :type input: dict + :return: None + :rtype: None + """ + element_id = kwargs.get("id", None) + pir_input = kwargs.get("input", None) query = """ mutation PirFlagElement($id: ID!, $input: PirFlagElementInput!) { pirFlagElement(id: $id, input: $input) @@ -15,14 +35,23 @@ def pir_flag_element(self, **kwargs): self.api.query( query, { - "id": id, - "input": input, + "id": element_id, + "input": pir_input, }, ) def pir_unflag_element(self, **kwargs): - id = kwargs.get("id", None) - input = kwargs.get("input", None) + """Unflag an element from a PIR. + + :param id: the element id + :type id: str + :param input: the PIR unflag input (PirUnflagElementInput format) + :type input: dict + :return: None + :rtype: None + """ + element_id = kwargs.get("id", None) + pir_input = kwargs.get("input", None) query = """ mutation PirUnflagElement($id: ID!, $input: PirUnflagElementInput!) { pirUnflagElement(id: $id, input: $input) @@ -31,7 +60,7 @@ def pir_unflag_element(self, **kwargs): self.api.query( query, { - "id": id, - "input": input, + "id": element_id, + "input": pir_input, }, ) diff --git a/client-python/pycti/api/opencti_api_playbook.py b/client-python/pycti/api/opencti_api_playbook.py index 96677fc0f3b0..7b206e95a4e9 100644 --- a/client-python/pycti/api/opencti_api_playbook.py +++ b/client-python/pycti/api/opencti_api_playbook.py @@ -1,10 +1,32 @@ class OpenCTIApiPlaybook: - """OpenCTIApiPlaybook""" + """OpenCTI Playbook API class. + + Manages playbook operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiPlaybook instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def playbook_step_execution(self, playbook: dict, bundle: str): + """Execute a playbook step. + + :param playbook: the playbook configuration dict containing execution_id, event_id, + execution_start, playbook_id, data_instance_id, step_id, previous_step_id, + and previous_bundle + :type playbook: dict + :param bundle: the STIX bundle to process + :type bundle: str + :return: None + :rtype: None + """ self.api.app_logger.info( "Executing playbook step", { @@ -34,8 +56,15 @@ def playbook_step_execution(self, playbook: dict, bundle: str): ) def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is not None: + """Delete a playbook. + + :param id: the playbook id + :type id: str + :return: None + :rtype: None + """ + playbook_id = kwargs.get("id", None) + if playbook_id is not None: query = """ mutation PlaybookDelete($id: ID!) { playbookDelete(id: $id) @@ -44,11 +73,11 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": playbook_id, }, ) else: self.api.app_logger.error( - "[stix_playbook] Cant delete playbook, missing parameters: id" + "[opencti_playbook] Cannot delete playbook, missing parameter: id" ) return None diff --git a/client-python/pycti/api/opencti_api_public_dashboard.py b/client-python/pycti/api/opencti_api_public_dashboard.py index f52c6a3cc66c..ad5e7c5e8a4f 100644 --- a/client-python/pycti/api/opencti_api_public_dashboard.py +++ b/client-python/pycti/api/opencti_api_public_dashboard.py @@ -1,12 +1,30 @@ class OpenCTIApiPublicDashboard: - """OpenCTIApiPublicDashboard""" + """OpenCTI Public Dashboard API class. + + Manages public dashboard operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiPublicDashboard instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is not None: + """Delete a public dashboard. + + :param id: the public dashboard id + :type id: str + :return: None + :rtype: None + """ + dashboard_id = kwargs.get("id", None) + if dashboard_id is not None: query = """ mutation PublicDashboardDelete($id: ID!) { publicDashboardDelete(id: $id) @@ -15,11 +33,11 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": dashboard_id, }, ) else: - self.opencti.app_logger.error( - "[stix_public_dashboard] Cant delete public dashboard, missing parameters: id" + self.api.app_logger.error( + "[opencti_public_dashboard] Cannot delete public dashboard, missing parameter: id" ) return None diff --git a/client-python/pycti/api/opencti_api_trash.py b/client-python/pycti/api/opencti_api_trash.py index eaf835f209d7..7b076c974a23 100644 --- a/client-python/pycti/api/opencti_api_trash.py +++ b/client-python/pycti/api/opencti_api_trash.py @@ -1,10 +1,28 @@ class OpenCTIApiTrash: - """OpenCTIApiTrash""" + """OpenCTI Trash API class. + + Manages trash/delete operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiTrash instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def restore(self, operation_id: str): + """Restore a deleted item from trash. + + :param operation_id: the delete operation id + :type operation_id: str + :return: None + :rtype: None + """ query = """ mutation DeleteOperationRestore($id: ID!) { deleteOperationRestore(id: $id) @@ -18,15 +36,17 @@ def restore(self, operation_id: str): ) def delete(self, **kwargs): - """Delete a trash item given its ID + """Delete a trash item given its ID. - :param id: ID for the delete operation on the platform. + :param id: ID for the delete operation on the platform :type id: str + :return: None + :rtype: None """ - id = kwargs.get("id", None) - if id is None: + delete_operation_id = kwargs.get("id", None) + if delete_operation_id is None: self.api.admin_logger.error( - "[opencti_trash] Cant confirm delete, missing parameter: id" + "[opencti_trash] Cannot confirm delete, missing parameter: id" ) return None query = """ @@ -37,6 +57,6 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": delete_operation_id, }, ) diff --git a/client-python/pycti/api/opencti_api_work.py b/client-python/pycti/api/opencti_api_work.py index dd230742497d..469b5bd51558 100644 --- a/client-python/pycti/api/opencti_api_work.py +++ b/client-python/pycti/api/opencti_api_work.py @@ -1,14 +1,34 @@ import time -from typing import Dict, List +from typing import Dict, List, Optional class OpenCTIApiWork: - """OpenCTIApiJob""" + """OpenCTI Work API class. + + Manages work/job operations for connectors. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiWork instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def to_received(self, work_id: str, message: str): + """Mark work as received. + + :param work_id: the work id + :type work_id: str + :param message: the message to report + :type message: str + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Reporting work update_received", {"work_id": work_id} @@ -23,6 +43,17 @@ def to_received(self, work_id: str, message: str): self.api.query(query, {"id": work_id, "message": message}, True) def to_processed(self, work_id: str, message: str, in_error: bool = False): + """Mark work as processed. + + :param work_id: the work id + :type work_id: str + :param message: the message to report + :type message: str + :param in_error: whether the work completed with error, defaults to False + :type in_error: bool, optional + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Reporting work update_processed", {"work_id": work_id} @@ -39,6 +70,13 @@ def to_processed(self, work_id: str, message: str, in_error: bool = False): ) def ping(self, work_id: str): + """Ping a work to keep it alive. + + :param work_id: the work id + :type work_id: str + :return: None + :rtype: None + """ self.api.app_logger.info("Ping work", {"work_id": work_id}) query = """ mutation pingWork($id: ID!) { @@ -50,6 +88,15 @@ def ping(self, work_id: str): self.api.query(query, {"id": work_id}) def report_expectation(self, work_id: str, error): + """Report a work expectation. + + :param work_id: the work id + :type work_id: str + :param error: the error to report (WorkErrorInput format) + :type error: dict + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info("Report expectation", {"work_id": work_id}) query = """ @@ -61,10 +108,19 @@ def report_expectation(self, work_id: str, error): """ try: self.api.query(query, {"id": work_id, "error": error}, True) - except: + except Exception: self.api.app_logger.error("Cannot report expectation") def add_expectations(self, work_id: str, expectations: int): + """Add expectations to a work. + + :param work_id: the work id + :type work_id: str + :param expectations: the number of expectations to add + :type expectations: int + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Update action expectations", @@ -81,10 +137,19 @@ def add_expectations(self, work_id: str, expectations: int): self.api.query( query, {"id": work_id, "expectations": expectations}, True ) - except: - self.api.app_logger.error("Cannot report expectation") + except Exception: + self.api.app_logger.error("Cannot add expectations") def add_draft_context(self, work_id: str, draft_context: str): + """Add draft context to a work. + + :param work_id: the work id + :type work_id: str + :param draft_context: the draft context to add + :type draft_context: str + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Update draft context", @@ -101,10 +166,19 @@ def add_draft_context(self, work_id: str, draft_context: str): self.api.query( query, {"id": work_id, "draftContext": draft_context}, True ) - except: - self.api.app_logger.error("Cannot report draft context") + except Exception: + self.api.app_logger.error("Cannot add draft context") + + def initiate_work(self, connector_id: str, friendly_name: str) -> Optional[str]: + """Initiate a new work for a connector. - def initiate_work(self, connector_id: str, friendly_name: str) -> str: + :param connector_id: the connector id + :type connector_id: str + :param friendly_name: the friendly name for the work + :type friendly_name: str + :return: the work id or None if bundle_send_to_queue is False + :rtype: str or None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info("Initiate work", {"connector_id": connector_id}) query = """ @@ -120,22 +194,33 @@ def initiate_work(self, connector_id: str, friendly_name: str) -> str: True, ) return work["data"]["workAdd"]["id"] + return None def delete_work(self, work_id: str): - query = """ - mutation ConnectorWorksMutation($workId: ID!) { - workEdit(id: $workId) { - delete - } - }""" - work = self.api.query(query, {"workId": work_id}, True) - return work["data"] + """Delete a work. + + .. deprecated:: + Use :meth:`delete` instead. + + :param work_id: the work id + :type work_id: str + :return: the response data + :rtype: dict + """ + return self.delete(id=work_id) def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is None: - self.opencti.admin_logger.error( - "[opencti_work] Cant delete work, missing parameter: id" + """Delete a work by id. + + :param id: the work id + :type id: str + :return: the response data + :rtype: dict or None + """ + work_id = kwargs.get("id", None) + if work_id is None: + self.api.admin_logger.error( + "[opencti_work] Cannot delete work, missing parameter: id" ) return None query = """ @@ -146,13 +231,19 @@ def delete(self, **kwargs): }""" work = self.api.query( query, - {"workId": id}, + {"workId": work_id}, ) return work["data"] def wait_for_work_to_finish(self, work_id: str): + """Wait for a work to finish. + + :param work_id: the work id + :type work_id: str + :return: empty string if error, None otherwise + :rtype: str or None + """ status = "" - cnt = 0 while status != "complete": state = self.get_work(work_id=work_id) if len(state) > 0: @@ -165,9 +256,15 @@ def wait_for_work_to_finish(self, work_id: str): return "" time.sleep(1) - cnt += 1 def get_work(self, work_id: str) -> Dict: + """Get a work by id. + + :param work_id: the work id + :type work_id: str + :return: the work data + :rtype: dict + """ query = """ query WorkQuery($id: ID!) { work(id: $id) { @@ -204,7 +301,14 @@ def get_work(self, work_id: str) -> Dict: result = self.api.query(query, {"id": work_id}, True) return result["data"]["work"] - def get_is_work_alive(self, work_id: str) -> Dict: + def get_is_work_alive(self, work_id: str) -> bool: + """Check if a work is alive. + + :param work_id: the work id + :type work_id: str + :return: whether the work is alive + :rtype: bool + """ query = """ query WorkAliveQuery($id: ID!) { isWorkAlive(id: $id) @@ -214,6 +318,13 @@ def get_is_work_alive(self, work_id: str) -> Dict: return result["data"]["isWorkAlive"] def get_connector_works(self, connector_id: str) -> List[Dict]: + """Get all works for a connector. + + :param connector_id: the connector id + :type connector_id: str + :return: list of work dictionaries sorted by timestamp + :rtype: list[dict] + """ query = """ query ConnectorWorksQuery( $count: Int diff --git a/client-python/pycti/api/opencti_api_workspace.py b/client-python/pycti/api/opencti_api_workspace.py index 2fcee4277b7b..7c7462e0c323 100644 --- a/client-python/pycti/api/opencti_api_workspace.py +++ b/client-python/pycti/api/opencti_api_workspace.py @@ -1,14 +1,32 @@ class OpenCTIApiWorkspace: - """OpenCTIApiWorkspace""" + """OpenCTI Workspace API class. + + Manages workspace operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiWorkspace instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is None: + """Delete a workspace. + + :param id: the workspace id + :type id: str + :return: None + :rtype: None + """ + workspace_id = kwargs.get("id", None) + if workspace_id is None: self.api.admin_logger.error( - "[opencti_workspace] Cant delete workspace, missing parameter: id" + "[opencti_workspace] Cannot delete workspace, missing parameter: id" ) return None query = """ @@ -19,6 +37,6 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": workspace_id, }, ) diff --git a/client-python/pycti/connector/opencti_connector.py b/client-python/pycti/connector/opencti_connector.py index e25b4eebf797..e6e3c171556a 100644 --- a/client-python/pycti/connector/opencti_connector.py +++ b/client-python/pycti/connector/opencti_connector.py @@ -1,37 +1,81 @@ -from enum import Enum +"""OpenCTI Connector module. + +This module defines the connector types and the main OpenCTIConnector class +used to register and configure connectors with the OpenCTI platform. +""" -# Scope definition -# EXTERNAL_IMPORT = None -# INTERNAL_IMPORT_FILE = Files mime types to support (application/json, ...) -# INTERNAL_ENRICHMENT = Entity types to support (Report, Hash, ...) -# INTERNAL_EXPORT_FILE = Files mime types to generate (application/pdf, ...) +from enum import Enum class ConnectorType(Enum): - EXTERNAL_IMPORT = "EXTERNAL_IMPORT" # From remote sources to OpenCTI stix2 - INTERNAL_IMPORT_FILE = ( - "INTERNAL_IMPORT_FILE" # From OpenCTI file system to OpenCTI stix2 - ) - INTERNAL_ENRICHMENT = "INTERNAL_ENRICHMENT" # From OpenCTI stix2 to OpenCTI stix2 - INTERNAL_ANALYSIS = "INTERNAL_ANALYSIS" # From OpenCTI file system or OpenCTI stix2 to OpenCTI file system - INTERNAL_EXPORT_FILE = ( - "INTERNAL_EXPORT_FILE" # From OpenCTI stix2 to OpenCTI file system - ) - STREAM = "STREAM" # Read the stream and do something + """Enumeration of OpenCTI connector types. + + Each connector type defines a specific data flow pattern: + + - EXTERNAL_IMPORT: Imports data from remote sources into OpenCTI as STIX2 + - INTERNAL_IMPORT_FILE: Converts files from OpenCTI file system to STIX2 + - INTERNAL_ENRICHMENT: Enriches existing STIX2 data with additional information + - INTERNAL_ANALYSIS: Analyzes files or STIX2 data and produces file output + - INTERNAL_EXPORT_FILE: Exports STIX2 data to files in OpenCTI file system + - STREAM: Reads the event stream and performs custom actions + + Scope definition varies by type: + - EXTERNAL_IMPORT: None (imports everything) + - INTERNAL_IMPORT_FILE: MIME types to support (e.g., application/json) + - INTERNAL_ENRICHMENT: Entity types to support (e.g., Report, Hash) + - INTERNAL_EXPORT_FILE: MIME types to generate (e.g., application/pdf) + """ + + EXTERNAL_IMPORT = "EXTERNAL_IMPORT" + INTERNAL_IMPORT_FILE = "INTERNAL_IMPORT_FILE" + INTERNAL_ENRICHMENT = "INTERNAL_ENRICHMENT" + INTERNAL_ANALYSIS = "INTERNAL_ANALYSIS" + INTERNAL_EXPORT_FILE = "INTERNAL_EXPORT_FILE" + STREAM = "STREAM" class OpenCTIConnector: - """Main class for OpenCTI connector + """Main class for OpenCTI connector registration and configuration. - :param connector_id: id for the connector (valid uuid4) + This class represents a connector instance that can be registered with + the OpenCTI platform. It holds all configuration parameters needed for + the connector to operate. + + :param connector_id: Unique identifier for the connector (valid UUID4) :type connector_id: str - :param connector_name: name for the connector + :param connector_name: Human-readable name for the connector :type connector_name: str - :param connector_type: valid OpenCTI connector type (see `ConnectorType`) + :param connector_type: Type of connector (see :class:`ConnectorType`) :type connector_type: str - :param scope: connector scope + :param scope: Connector scope as a comma-separated string (e.g., "Report,Indicator") :type scope: str - :raises ValueError: if the connector type is not valid + :param auto: Whether the connector runs automatically on matching entities + :type auto: bool + :param only_contextual: Whether the connector only processes contextual data + :type only_contextual: bool + :param playbook_compatible: Whether the connector can be used in playbooks + :type playbook_compatible: bool + :param auto_update: Whether to automatically update existing entities + :type auto_update: bool + :param enrichment_resolution: Strategy for resolving enrichment conflicts + :type enrichment_resolution: str + :param listen_callback_uri: Optional callback URI for API-based listening + :type listen_callback_uri: str or None + + :raises ValueError: If the connector type is not a valid ConnectorType value + + Example: + >>> connector = OpenCTIConnector( + ... connector_id="550e8400-e29b-41d4-a716-446655440000", + ... connector_name="My Connector", + ... connector_type="EXTERNAL_IMPORT", + ... scope="Report,Indicator", + ... auto=False, + ... only_contextual=False, + ... playbook_compatible=True, + ... auto_update=False, + ... enrichment_resolution="none" + ... ) """ def __init__( @@ -47,12 +91,35 @@ def __init__( enrichment_resolution: str, listen_callback_uri=None, ): + """Initialize the OpenCTIConnector instance. + + :param connector_id: Unique identifier for the connector (valid UUID4) + :type connector_id: str + :param connector_name: Human-readable name for the connector + :type connector_name: str + :param connector_type: Type of connector (see :class:`ConnectorType`) + :type connector_type: str + :param scope: Connector scope as a comma-separated string + :type scope: str + :param auto: Whether the connector runs automatically + :type auto: bool + :param only_contextual: Whether to process only contextual data + :type only_contextual: bool + :param playbook_compatible: Whether the connector works with playbooks + :type playbook_compatible: bool + :param auto_update: Whether to auto-update existing entities + :type auto_update: bool + :param enrichment_resolution: Enrichment conflict resolution strategy + :type enrichment_resolution: str + :param listen_callback_uri: Optional callback URI for API listening + :type listen_callback_uri: str or None + + :raises ValueError: If connector_type is not a valid ConnectorType + """ self.id = connector_id self.name = connector_name self.type = ConnectorType(connector_type) - if self.type is None: - raise ValueError("Invalid connector type: " + connector_type) - if scope and len(scope) > 0: + if scope: self.scope = scope.split(",") else: self.scope = [] @@ -64,10 +131,17 @@ def __init__( self.listen_callback_uri = listen_callback_uri def to_input(self) -> dict: - """connector input to use in API query + """Convert connector configuration to API input format. - :return: dict with connector data + Generates a dictionary structure suitable for use in GraphQL API + queries to register or update the connector. + + :return: Dictionary containing connector data wrapped in an "input" key :rtype: dict + + Example: + >>> connector.to_input() + {'input': {'id': '...', 'name': 'My Connector', ...}} """ return { "input": { diff --git a/client-python/pycti/connector/opencti_connector_helper.py b/client-python/pycti/connector/opencti_connector_helper.py index 052b29907902..7e660bc35249 100644 --- a/client-python/pycti/connector/opencti_connector_helper.py +++ b/client-python/pycti/connector/opencti_connector_helper.py @@ -1,3 +1,22 @@ +"""OpenCTI Connector Helper module. + +This module provides the main helper class and utilities for building OpenCTI +connectors. It handles connector registration, message queue communication, +stream listening, scheduling, and STIX2 bundle processing. + +Key components: + - OpenCTIConnectorHelper: Main class for connector development + - ListenQueue: Handles RabbitMQ message consumption + - ListenStream: Handles SSE stream consumption + - PingAlive: Maintains connector heartbeat with the platform + - ConnectorInfo: Stores connector runtime information + +Example: + >>> from pycti import OpenCTIConnectorHelper + >>> helper = OpenCTIConnectorHelper(config) + >>> helper.listen(callback_function) +""" + import asyncio import base64 import copy @@ -18,8 +37,10 @@ from queue import Queue from typing import Callable, Dict, List, Optional, Union +import boto3 import pika import uvicorn +from botocore.config import Config as BotoConfig from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from filigran_sseclient import SSEClient @@ -32,23 +53,36 @@ from pycti.utils.opencti_stix2_splitter import OpenCTIStix2Splitter TRUTHY: List[str] = ["yes", "true", "True"] +"""List of string values considered as boolean True.""" + FALSY: List[str] = ["no", "false", "False"] +"""List of string values considered as boolean False.""" app = FastAPI() -def killProgramHook(etype, value, tb): - """Exception hook to terminate the program. +def killProgramHook(etype, value, tb) -> None: + """Exception hook to terminate the program on unhandled exceptions. + + This function is used as a system exception hook to ensure the program + terminates cleanly when an unhandled exception occurs, particularly + useful for background threads. - :param etype: Exception type - :param value: Exception value - :param tb: Traceback object + :param etype: The exception type (class) + :type etype: type + :param value: The exception instance + :type value: BaseException + :param tb: The traceback object + :type tb: types.TracebackType """ os.kill(os.getpid(), signal.SIGTERM) -def start_loop(loop): - """Start an asyncio event loop. +def start_loop(loop) -> None: + """Start an asyncio event loop and run it forever. + + Sets the given event loop as the current loop for the thread and + runs it indefinitely until stopped. :param loop: The asyncio event loop to start :type loop: asyncio.AbstractEventLoop @@ -60,19 +94,48 @@ def start_loop(loop): def get_config_variable( env_var: str, yaml_path: List, - config: Dict = {}, + config: Optional[Dict] = None, isNumber: Optional[bool] = False, default=None, required=False, ) -> Union[bool, int, None, str]: - """[summary] + """Retrieve a configuration variable from environment or YAML config. + + Looks up configuration values with the following precedence: + 1. Environment variable (highest priority) + 2. YAML configuration file + 3. Default value (lowest priority) - :param env_var: environment variable name - :param yaml_path: path to yaml config - :param config: client config dict, defaults to {} - :param isNumber: specify if the variable is a number, defaults to False - :param default: default value + Boolean string values ("yes", "true", "True", "no", "false", "False") + are automatically converted to Python bool. + + :param env_var: Name of the environment variable to check + :type env_var: str + :param yaml_path: Two-element list specifying [section, key] in YAML config + :type yaml_path: List[str] + :param config: Configuration dictionary loaded from YAML file + :type config: Dict + :param isNumber: If True, convert the value to integer + :type isNumber: bool + :param default: Default value if not found in env or config + :type default: any + :param required: If True and no value found, raise ValueError + :type required: bool + + :return: The configuration value as bool, int, str, or None + :rtype: Union[bool, int, None, str] + + :raises ValueError: If required=True and no value is found + + Example: + >>> get_config_variable("OPENCTI_URL", ["opencti", "url"], config) + 'http://localhost:8080' + >>> get_config_variable("CONNECTOR_LOG_LEVEL", ["connector", "log_level"], + ... config, default="INFO") + 'INFO' """ + if config is None: + config = {} if os.getenv(env_var) is not None: result = os.getenv(env_var) @@ -105,26 +168,27 @@ def get_config_variable( def normalize_email_prefix(email: str) -> str: - """ - Normalize the prefix (local part) of an email address by replacing - invalid characters with hyphens. + """Normalize the local part of an email address by replacing invalid characters. - Valid characters in email prefix: a-z, A-Z, 0-9, and special chars: . _ + - - All other characters are replaced with '-' + Replaces any characters not valid in email prefixes with hyphens. + Valid characters include: a-z, A-Z, 0-9, and special chars: . _ + - + Consecutive hyphens are collapsed and leading/trailing hyphens are removed. - Args: - email: Email address to normalize + :param email: Email address to normalize + :type email: str - Returns: - Normalized email address + :return: Normalized email address with valid local part + :rtype: str - Examples: + :raises ValueError: If the email address does not contain an '@' symbol + + Example: >>> normalize_email_prefix("john.doe@example.com") 'john.doe@example.com' >>> normalize_email_prefix("john@doe@example.com") 'john-doe@example.com' - >>> normalize_email_prefix("user!name#test@example.com") - 'user-name-test@example.com' + >>> normalize_email_prefix("user!name@domain.com") + 'user-name@domain.com' """ if "@" not in email: raise ValueError("Invalid email: missing '@' symbol") @@ -149,24 +213,43 @@ def normalize_email_prefix(email: str) -> str: return f"{normalized_prefix}@{domain}" -def is_memory_certificate(certificate): +def is_memory_certificate(certificate: str) -> bool: """Check if a certificate is provided as a PEM string in memory. - :param certificate: The certificate data to check + Determines whether the certificate data is an in-memory PEM-formatted + string (starting with "-----BEGIN") rather than a file path. + + :param certificate: The certificate data to check (PEM string or file path) :type certificate: str - :return: True if the certificate is a PEM string, False otherwise + + :return: True if the certificate is a PEM string, False if it's a file path :rtype: bool + + Example: + >>> is_memory_certificate("-----BEGIN CERTIFICATE-----\\n...") + True + >>> is_memory_certificate("/path/to/cert.pem") + False """ return certificate.startswith("-----BEGIN") -def ssl_verify_locations(ssl_context, certdata): - """Load certificate verification locations into SSL context. +def ssl_verify_locations(ssl_context: ssl.SSLContext, certdata: Optional[str]) -> None: + """Load CA certificate verification locations into an SSL context. + + Configures the SSL context with certificate authority (CA) certificates + for verifying peer certificates. Supports both file paths and in-memory + PEM-formatted certificates. :param ssl_context: The SSL context to configure :type ssl_context: ssl.SSLContext - :param certdata: Certificate data (file path or PEM string) + :param certdata: CA certificate data as file path or PEM string, or None to skip :type certdata: str or None + + Example: + >>> ssl_ctx = ssl.create_default_context() + >>> ssl_verify_locations(ssl_ctx, "/path/to/ca-bundle.crt") + >>> ssl_verify_locations(ssl_ctx, "-----BEGIN CERTIFICATE-----\\n...") """ if certdata is None: return @@ -177,16 +260,27 @@ def ssl_verify_locations(ssl_context, certdata): ssl_context.load_verify_locations(cafile=certdata) -def data_to_temp_file(data): +def data_to_temp_file(data: str) -> str: """Write data to a temporary file securely. - Creates a temporary file in the most secure manner possible. - The file is readable and writable only by the creating user ID. + Creates a temporary file with secure permissions (readable and writable + only by the creating user). The file descriptor is not inherited by + child processes. + + .. note:: + The caller is responsible for deleting the temporary file when + it is no longer needed. - :param data: The data to write to the temporary file + :param data: The string data to write to the temporary file :type data: str - :return: Path to the created temporary file + + :return: Absolute path to the created temporary file :rtype: str + + Example: + >>> path = data_to_temp_file("-----BEGIN PRIVATE KEY-----\\n...") + >>> # Use the file... + >>> os.unlink(path) # Clean up when done """ # The file is readable and writable only by the creating user ID. # If the operating system uses permission bits to indicate whether a @@ -195,21 +289,34 @@ def data_to_temp_file(data): file_descriptor, file_path = tempfile.mkstemp() with os.fdopen(file_descriptor, "w") as open_file: open_file.write(data) - open_file.close() return file_path -def ssl_cert_chain(ssl_context, cert_data, key_data, passphrase): - """Load certificate chain into SSL context. +def ssl_cert_chain( + ssl_context: ssl.SSLContext, + cert_data: Optional[str], + key_data: Optional[str], + passphrase: Optional[str], +) -> None: + """Load a certificate chain and private key into an SSL context. + + Configures the SSL context with a client certificate and private key + for mutual TLS authentication. Supports both file paths and in-memory + PEM-formatted certificates/keys. Temporary files are created and + cleaned up automatically when using in-memory data. :param ssl_context: The SSL context to configure :type ssl_context: ssl.SSLContext - :param cert_data: Certificate data (file path or PEM string) + :param cert_data: Certificate data as file path or PEM string, or None to skip :type cert_data: str or None - :param key_data: Private key data (file path or PEM string) + :param key_data: Private key data as file path or PEM string, or None :type key_data: str or None - :param passphrase: Passphrase for the private key + :param passphrase: Passphrase for encrypted private key, or None if unencrypted :type passphrase: str or None + + Example: + >>> ssl_ctx = ssl.create_default_context() + >>> ssl_cert_chain(ssl_ctx, "/path/to/cert.pem", "/path/to/key.pem", None) """ if cert_data is None: return @@ -236,12 +343,22 @@ def ssl_cert_chain(ssl_context, cert_data, key_data, passphrase): os.unlink(key_file_path) -def create_callback_ssl_context(config) -> ssl.SSLContext: - """Create SSL context for API callback server. +def create_callback_ssl_context(config: Dict) -> ssl.SSLContext: + """Create an SSL context for the API callback server. - :param config: Configuration dictionary - :type config: dict - :return: Configured SSL context + Creates and configures an SSL context suitable for the HTTPS callback + server used in API listen protocol mode. Loads certificate chain from + configuration. + + Configuration keys used: + - LISTEN_PROTOCOL_API_SSL_KEY: Path or PEM string for SSL private key + - LISTEN_PROTOCOL_API_SSL_CERT: Path or PEM string for SSL certificate + - LISTEN_PROTOCOL_API_SSL_PASSPHRASE: Optional passphrase for private key + + :param config: Configuration dictionary containing SSL settings + :type config: Dict + + :return: Configured SSL context for client authentication :rtype: ssl.SSLContext """ listen_protocol_api_ssl_key = get_config_variable( @@ -272,12 +389,24 @@ def create_callback_ssl_context(config) -> ssl.SSLContext: return ssl_context -def create_mq_ssl_context(config) -> ssl.SSLContext: - """Create SSL context for message queue connections. +def create_mq_ssl_context(config: Dict) -> ssl.SSLContext: + """Create an SSL context for RabbitMQ message queue connections. - :param config: Configuration dictionary - :type config: dict - :return: Configured SSL context for MQ connections + Creates and configures an SSL context for secure connections to RabbitMQ. + Supports CA verification, client certificates, and optional certificate + verification bypass. + + Configuration keys used: + - MQ_USE_SSL_CA: CA certificate for server verification + - MQ_USE_SSL_CERT: Client certificate for mutual TLS + - MQ_USE_SSL_KEY: Client private key for mutual TLS + - MQ_USE_SSL_REJECT_UNAUTHORIZED: Whether to verify server certificate + - MQ_USE_SSL_PASSPHRASE: Optional passphrase for private key + + :param config: Configuration dictionary containing MQ SSL settings + :type config: Dict + + :return: Configured SSL context for RabbitMQ connections :rtype: ssl.SSLContext """ use_ssl_ca = get_config_variable("MQ_USE_SSL_CA", ["mq", "use_ssl_ca"], config) @@ -308,29 +437,74 @@ def create_mq_ssl_context(config) -> ssl.SSLContext: class ListenQueue(threading.Thread): - """Main class for the ListenQueue used in OpenCTIConnectorHelper + """Thread class for consuming messages from RabbitMQ or HTTP API. + + Handles message consumption from either RabbitMQ (AMQP protocol) or + an HTTP API endpoint, depending on the configured listen protocol. + Messages are processed through a callback function provided at initialization. - :param helper: instance of a `OpenCTIConnectorHelper` class + This class supports two listen protocols: + - AMQP: Connects to RabbitMQ and consumes messages from a queue + - API: Starts an HTTP server and receives messages via POST requests + + :param helper: The OpenCTIConnectorHelper instance :type helper: OpenCTIConnectorHelper - :param config: dict containing client config + :param opencti_token: Authentication token for OpenCTI API + :type opencti_token: str + :param config: Global configuration dictionary :type config: Dict - :param callback: callback function to process queue - :type callback: callable + :param connector_config: Connector-specific configuration from registration + :type connector_config: Dict + :param applicant_id: ID of the user/connector making requests + :type applicant_id: str + :param listen_protocol: Protocol to use ("AMQP" or "API") + :type listen_protocol: str + :param listen_protocol_api_ssl: Whether to use SSL for API protocol + :type listen_protocol_api_ssl: bool + :param listen_protocol_api_path: URL path for API endpoint + :type listen_protocol_api_path: str + :param listen_protocol_api_port: Port for API server + :type listen_protocol_api_port: int + :param callback: Function to call when processing messages + :type callback: Callable[[Dict], str] """ def __init__( self, helper, - opencti_token, + opencti_token: str, config: Dict, connector_config: Dict, - applicant_id, - listen_protocol, - listen_protocol_api_ssl, - listen_protocol_api_path, - listen_protocol_api_port, - callback, + applicant_id: str, + listen_protocol: str, + listen_protocol_api_ssl: bool, + listen_protocol_api_path: str, + listen_protocol_api_port: int, + callback: Callable[[Dict], str], ) -> None: + """Initialize the ListenQueue thread. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param opencti_token: Authentication token for OpenCTI API + :type opencti_token: str + :param config: Global configuration dictionary + :type config: Dict + :param connector_config: Connector configuration from registration + :type connector_config: Dict + :param applicant_id: ID of the user/connector making requests + :type applicant_id: str + :param listen_protocol: Protocol to use ("AMQP" or "API") + :type listen_protocol: str + :param listen_protocol_api_ssl: Whether to use SSL for API protocol + :type listen_protocol_api_ssl: bool + :param listen_protocol_api_path: URL path for API endpoint + :type listen_protocol_api_path: str + :param listen_protocol_api_port: Port for API server + :type listen_protocol_api_port: int + :param callback: Function to process received messages + :type callback: Callable[[Dict], str] + """ threading.Thread.__init__(self) self.pika_credentials = None self.pika_parameters = None @@ -357,16 +531,20 @@ def __init__( # noinspection PyUnusedLocal def _process_message(self, channel, method, properties, body) -> None: - """process a message from the rabbit queue - - :param channel: channel instance - :type channel: callable - :param method: message methods - :type method: callable - :param properties: unused - :type properties: str - :param body: message body (data) - :type body: str or bytes or bytearray + """Process a message from the RabbitMQ queue. + + Acknowledges the message immediately before processing to prevent + infinite re-delivery if the connector fails. Spawns a separate thread + for data handling and maintains the connection alive during processing. + + :param channel: The RabbitMQ channel instance + :type channel: pika.channel.Channel + :param method: Message delivery method with routing info and delivery tag + :type method: pika.spec.Basic.Deliver + :param properties: Message properties (unused) + :type properties: pika.spec.BasicProperties + :param body: The message body containing JSON data + :type body: bytes """ json_data = json.loads(body) # Message should be ack before processing as we don't own the processing @@ -405,7 +583,21 @@ def _set_draft_id(self, draft_id): self.helper.api.set_draft_id(draft_id) self.helper.api_impersonate.set_draft_id(draft_id) - def _data_handler(self, json_data) -> None: + def _data_handler(self, json_data: Dict) -> None: + """Process incoming message data and execute the callback. + + Handles the full message processing workflow including: + - Extracting event data and entity information + - Setting up draft and work contexts + - Resolving enrichment entity data for enrichment connectors + - Handling playbook execution context + - Managing organization sharing propagation + - Executing the user-provided callback function + + :param json_data: The parsed JSON message data containing event and internal info + :type json_data: Dict + """ + work_id = None # Execute the callback try: event_data = json_data["event"] @@ -448,7 +640,7 @@ def _data_handler(self, json_data) -> None: opencti_entity = do_read(id=entity_id, withFiles=True) if opencti_entity is None: raise ValueError( - "Unable to read/access to the entity, please check that the connector permission" + "Unable to read/access the entity, please check the connector permissions" ) event_data["enrichment_entity"] = opencti_entity # Handle action vs playbook behavior @@ -513,7 +705,7 @@ def _data_handler(self, json_data) -> None: ) ) - # Handle applicant_id for in-personalization + # Handle applicant_id for impersonation self.helper.applicant_id = self.connector_applicant_id self.helper.api_impersonate.set_applicant_id_header( self.connector_applicant_id @@ -542,13 +734,30 @@ def _data_handler(self, json_data) -> None: if work_id: try: self.helper.api.work.to_processed(work_id, str(e), True) - except: # pylint: disable=bare-except + except Exception: # pylint: disable=broad-except self.helper.metric.inc("error_count") self.helper.connector_logger.error( "Failing reporting the processing" ) - async def _http_process_callback(self, request: Request): + async def _http_process_callback(self, request: Request) -> JSONResponse: + """Handle incoming HTTP POST requests for API listen protocol. + + Validates the request authentication using Bearer token and processes + the JSON payload through the data handler. + + :param request: The incoming FastAPI request object + :type request: Request + + :return: JSON response with status code and message + :rtype: JSONResponse + + Response codes: + - 202: Message successfully received and queued for processing + - 400: Invalid JSON payload + - 401: Invalid or missing authentication credentials + - 500: Error occurred during message processing + """ # 01. Check the authentication authorization: str = request.headers.get("Authorization", "") items = authorization.split() if isinstance(authorization, str) else [] @@ -587,12 +796,22 @@ async def _http_process_callback(self, request: Request): ) def run(self) -> None: + """Execute the message listening thread. + + Starts the appropriate listener based on the configured protocol: + - AMQP: Connects to RabbitMQ and consumes messages from the queue + - API: Starts a FastAPI/Uvicorn HTTP server to receive messages + + The thread runs until stopped via the stop() method or an error occurs. + + :raises ValueError: If an unsupported listen protocol is configured + """ if self.listen_protocol == "AMQP": self.helper.connector_logger.info("Starting ListenQueue thread") while not self.exit_event.is_set(): try: self.helper.connector_logger.info( - "ListenQueue connecting to rabbitMq." + "ListenQueue connecting to RabbitMQ." ) # Connect the broker self.pika_credentials = pika.PlainCredentials( @@ -616,7 +835,7 @@ def run(self) -> None: self.pika_connection = pika.BlockingConnection(self.pika_parameters) self.channel = self.pika_connection.channel() try: - # confirm_delivery is only for cluster mode rabbitMQ + # confirm_delivery is only for cluster mode RabbitMQ # when not in cluster mode this line raise an exception self.channel.confirm_delivery() except Exception as err: # pylint: disable=broad-except @@ -679,16 +898,54 @@ def stop(self): class PingAlive(threading.Thread): + """Daemon thread that maintains connector heartbeat with OpenCTI platform. + + Periodically pings the OpenCTI API to indicate the connector is alive + and synchronizes connector state between local and remote instances. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param connector_id: Unique identifier of the connector + :type connector_id: str + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + :param get_state: Function to retrieve current connector state + :type get_state: Callable[[], Optional[Dict]] + :param set_state: Function to update connector state + :type set_state: Callable[[str], None] + :param metric: Metric handler for recording ping statistics + :type metric: OpenCTIMetricHandler + :param connector_info: ConnectorInfo instance with runtime details + :type connector_info: ConnectorInfo + """ + def __init__( self, connector_logger, - connector_id, + connector_id: str, api, - get_state, - set_state, + get_state: Callable[[], Optional[Dict]], + set_state: Callable[[str], None], metric, connector_info, ) -> None: + """Initialize the PingAlive daemon thread. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param connector_id: Unique identifier of the connector + :type connector_id: str + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + :param get_state: Function to retrieve current connector state + :type get_state: Callable[[], Optional[Dict]] + :param set_state: Function to update connector state + :type set_state: Callable[[str], None] + :param metric: Metric handler for recording ping statistics + :type metric: OpenCTIMetricHandler + :param connector_info: ConnectorInfo instance with runtime details + :type connector_info: ConnectorInfo + """ threading.Thread.__init__(self, daemon=True) self.connector_logger = connector_logger self.connector_id = connector_id @@ -701,6 +958,18 @@ def __init__( self.connector_info = connector_info def ping(self) -> None: + """Execute the ping loop to maintain connector heartbeat. + + Continuously pings the OpenCTI API every 40 seconds to: + - Signal that the connector is alive + - Send current connector state and info + - Receive and apply any remote state updates + + If the remote state differs from local state, the local state + is updated to match. This allows state resets from the UI. + + The loop continues until the exit_event is set. + """ while not self.exit_event.is_set(): try: self.connector_logger.debug("PingAlive running.") @@ -736,22 +1005,55 @@ def ping(self) -> None: self.exit_event.wait(40) def run(self) -> None: + """Start the PingAlive thread execution. + + Entry point for the thread that initiates the ping loop. + """ self.connector_logger.info("Starting PingAlive thread") self.ping() def stop(self) -> None: + """Stop the PingAlive thread gracefully. + + Sets the exit event to signal the ping loop to terminate. + """ self.connector_logger.info("Preparing PingAlive for clean shutdown") self.exit_event.set() class StreamAlive(threading.Thread): - def __init__(self, helper, q) -> None: + """Watchdog thread that monitors SSE stream health via heartbeat messages. + + Monitors a queue for heartbeat signals from the stream listener. + If no heartbeat is received within 45 seconds, the connector is + stopped to allow for reconnection. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param q: Queue for receiving heartbeat signals from stream listener + :type q: Queue + """ + + def __init__(self, helper, q: Queue) -> None: + """Initialize the StreamAlive watchdog thread. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param q: Queue for receiving heartbeat signals + :type q: Queue + """ threading.Thread.__init__(self) self.helper = helper self.q = q self.exit_event = threading.Event() def run(self) -> None: + """Execute the stream health monitoring loop. + + Checks every 5 seconds for heartbeat signals in the queue. + If no signal is received for 45 seconds, terminates the connector + to trigger a reconnection. + """ try: self.helper.connector_logger.info("Starting StreamAlive thread") time_since_last_heartbeat = 0 @@ -779,25 +1081,84 @@ def run(self) -> None: sys.excepthook(*sys.exc_info()) def stop(self) -> None: + """Stop the StreamAlive watchdog thread gracefully. + + Sets the exit event to signal the monitoring loop to terminate. + """ self.helper.connector_logger.info("Preparing StreamAlive for clean shutdown") self.exit_event.set() class ListenStream(threading.Thread): + """Thread class for consuming events from OpenCTI SSE stream. + + Connects to an OpenCTI event stream and processes events through + a callback function. Supports recovery from a specific point in time + and various filtering options. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param callback: Function to call for each stream event + :type callback: Callable + :param url: Base URL for the stream endpoint + :type url: str + :param token: Authentication token for the stream + :type token: str + :param verify_ssl: Whether to verify SSL certificates + :type verify_ssl: bool + :param start_timestamp: Timestamp to start reading from (format: "timestamp-0") + :type start_timestamp: str or None + :param live_stream_id: ID of the specific stream to connect to + :type live_stream_id: str or None + :param listen_delete: Whether to receive delete events + :type listen_delete: bool + :param no_dependencies: Whether to exclude dependency objects + :type no_dependencies: bool + :param recover_iso_date: ISO date to recover events from + :type recover_iso_date: str or None + :param with_inferences: Whether to include inferred relationships + :type with_inferences: bool + """ + def __init__( self, helper, - callback, - url, - token, - verify_ssl, - start_timestamp, - live_stream_id, - listen_delete, - no_dependencies, - recover_iso_date, - with_inferences, + callback: Callable, + url: str, + token: str, + verify_ssl: bool, + start_timestamp: Optional[str], + live_stream_id: Optional[str], + listen_delete: bool, + no_dependencies: bool, + recover_iso_date: Optional[str], + with_inferences: bool, ) -> None: + """Initialize the ListenStream thread. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param callback: Function to process stream events + :type callback: Callable + :param url: Base URL for the stream endpoint + :type url: str + :param token: Authentication token + :type token: str + :param verify_ssl: Whether to verify SSL certificates + :type verify_ssl: bool + :param start_timestamp: Starting timestamp for stream position + :type start_timestamp: str or None + :param live_stream_id: Specific stream ID to connect to + :type live_stream_id: str or None + :param listen_delete: Whether to receive delete events + :type listen_delete: bool + :param no_dependencies: Whether to exclude dependencies + :type no_dependencies: bool + :param recover_iso_date: ISO date for event recovery + :type recover_iso_date: str or None + :param with_inferences: Whether to include inferences + :type with_inferences: bool + """ threading.Thread.__init__(self) self.helper = helper self.callback = callback @@ -813,6 +1174,17 @@ def __init__( self.exit_event = threading.Event() def run(self) -> None: # pylint: disable=too-many-branches + """Execute the stream listening loop. + + Connects to the OpenCTI SSE stream and processes events: + - Initializes or restores stream position from connector state + - Starts a StreamAlive watchdog for health monitoring + - Processes heartbeat, connected, and data events + - Updates connector state with latest event ID + - Calls the callback function for each data event + + The loop continues until stopped or an error occurs. + """ try: self.helper.connector_logger.info("Starting ListenStream thread") current_state = self.helper.get_state() @@ -850,7 +1222,7 @@ def run(self) -> None: # pylint: disable=too-many-branches stream_alive.start() # Computing args building live_stream_url = self.url - # In case no recover is explicitely set + # In case no recover is explicitly set if recover_until is not False and recover_until not in [ "no", "none", @@ -928,6 +1300,31 @@ def stop(self): class ConnectorInfo: + """Container for connector runtime information and status. + + Stores runtime metrics and status information about the connector, + which is sent to the OpenCTI platform during ping operations. + + :param run_and_terminate: Whether connector runs once and terminates + :type run_and_terminate: bool + :param buffering: Whether connector is currently buffering due to queue limits + :type buffering: bool + :param queue_threshold: Maximum allowed queue size in MB before buffering + :type queue_threshold: float + :param queue_messages_size: Current size of queued messages in MB + :type queue_messages_size: float + :param next_run_datetime: Scheduled datetime for next connector run + :type next_run_datetime: datetime or None + :param last_run_datetime: Datetime of the last connector run + :type last_run_datetime: datetime or None + + Example: + >>> info = ConnectorInfo(run_and_terminate=False, queue_threshold=500.0) + >>> info.buffering = True + >>> info.queue_messages_size = 450.0 + >>> details = info.all_details + """ + def __init__( self, run_and_terminate: bool = False, @@ -937,6 +1334,21 @@ def __init__( next_run_datetime: datetime = None, last_run_datetime: datetime = None, ): + """Initialize ConnectorInfo with runtime parameters. + + :param run_and_terminate: Whether connector runs once and terminates + :type run_and_terminate: bool + :param buffering: Whether connector is buffering + :type buffering: bool + :param queue_threshold: Maximum queue size in MB + :type queue_threshold: float + :param queue_messages_size: Current queue size in MB + :type queue_messages_size: float + :param next_run_datetime: Next scheduled run time + :type next_run_datetime: datetime or None + :param last_run_datetime: Last run time + :type last_run_datetime: datetime or None + """ self._run_and_terminate = run_and_terminate self._buffering = buffering self._queue_threshold = queue_threshold @@ -962,10 +1374,15 @@ def all_details(self): @property def run_and_terminate(self) -> bool: + """Get the run_and_terminate flag. + + :return: Whether the connector runs once and terminates + :rtype: bool + """ return self._run_and_terminate @run_and_terminate.setter - def run_and_terminate(self, value): + def run_and_terminate(self, value: bool) -> None: """Set the run_and_terminate flag. :param value: Whether the connector should run once and terminate @@ -975,10 +1392,15 @@ def run_and_terminate(self, value): @property def buffering(self) -> bool: + """Get the buffering status. + + :return: Whether the connector is currently buffering + :rtype: bool + """ return self._buffering @buffering.setter - def buffering(self, value): + def buffering(self, value: bool) -> None: """Set the buffering status. :param value: Whether the connector is currently buffering @@ -988,10 +1410,15 @@ def buffering(self, value): @property def queue_threshold(self) -> float: + """Get the queue threshold value. + + :return: Maximum allowed queue size in MB + :rtype: float + """ return self._queue_threshold @queue_threshold.setter - def queue_threshold(self, value): + def queue_threshold(self, value: float) -> None: """Set the queue threshold value. :param value: The queue size threshold in MB @@ -1001,10 +1428,15 @@ def queue_threshold(self, value): @property def queue_messages_size(self) -> float: + """Get the current queue messages size. + + :return: Current size of queued messages in MB + :rtype: float + """ return self._queue_messages_size @queue_messages_size.setter - def queue_messages_size(self, value): + def queue_messages_size(self, value: float) -> None: """Set the current queue messages size. :param value: The current size of messages in the queue in MB @@ -1014,10 +1446,15 @@ def queue_messages_size(self, value): @property def next_run_datetime(self) -> datetime: + """Get the next scheduled run datetime. + + :return: Datetime for the next scheduled run, or None if not scheduled + :rtype: datetime or None + """ return self._next_run_datetime @next_run_datetime.setter - def next_run_datetime(self, value): + def next_run_datetime(self, value: datetime) -> None: """Set the next scheduled run datetime. :param value: The datetime for the next scheduled run @@ -1027,10 +1464,15 @@ def next_run_datetime(self, value): @property def last_run_datetime(self) -> datetime: + """Get the last run datetime. + + :return: Datetime of the last connector run, or None if never run + :rtype: datetime or None + """ return self._last_run_datetime @last_run_datetime.setter - def last_run_datetime(self, value): + def last_run_datetime(self, value: datetime) -> None: """Set the last run datetime. :param value: The datetime of the last run @@ -1040,13 +1482,50 @@ def last_run_datetime(self, value): class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods - """Python API for OpenCTI connector + """Main helper class for developing OpenCTI connectors. - :param config: dict standard config + Provides a comprehensive API for connector development, handling: + - Connector registration and configuration + - Message queue communication (RabbitMQ/API) + - SSE stream consumption + - STIX2 bundle creation and submission + - Scheduling and lifecycle management + - Metrics and logging + + :param config: Configuration dictionary containing OpenCTI and connector settings :type config: Dict + :param playbook_compatible: Whether the connector can be used in playbooks + :type playbook_compatible: bool + + Example: + >>> config = { + ... "opencti": {"url": "http://localhost:8080", "token": "xxx"}, + ... "connector": {"id": "xxx", "name": "My Connector", "type": "EXTERNAL_IMPORT"} + ... } + >>> helper = OpenCTIConnectorHelper(config) + >>> helper.listen(my_callback_function) + + Attributes: + api: OpenCTI API client for connector operations + api_impersonate: API client that impersonates the request applicant + connector_logger: Logger instance for connector messages + connector_info: Runtime information about the connector + metric: Prometheus metric handler """ class TimeUnit(Enum): + """Time unit enumeration for scheduling intervals (deprecated). + + Use ISO 8601 duration format with schedule_iso() instead. + + :cvar SECONDS: 1 second + :cvar MINUTES: 60 seconds + :cvar HOURS: 3600 seconds + :cvar DAYS: 86400 seconds + :cvar WEEKS: 604800 seconds + :cvar YEARS: 31536000 seconds + """ + SECONDS = 1 MINUTES = 60 HOURS = 3600 @@ -1054,7 +1533,14 @@ class TimeUnit(Enum): WEEKS = 604800 YEARS = 31536000 - def __init__(self, config: Dict, playbook_compatible=False) -> None: + def __init__(self, config: Dict, playbook_compatible: bool = False) -> None: + """Initialize the OpenCTIConnectorHelper. + + :param config: Configuration dictionary with OpenCTI and connector settings + :type config: Dict + :param playbook_compatible: Whether the connector can be used in playbooks + :type playbook_compatible: bool + """ sys.excepthook = killProgramHook # Cache @@ -1219,6 +1705,50 @@ def __init__(self, config: Dict, playbook_compatible=False) -> None: isNumber=True, default=7, ) + # S3 send mode configuration + self.bundle_send_to_s3 = get_config_variable( + "CONNECTOR_SEND_TO_S3", + ["connector", "send_to_s3"], + config, + default=False, + ) + self.bundle_send_to_s3_bucket = get_config_variable( + "CONNECTOR_SEND_TO_S3_BUCKET", + ["connector", "send_to_s3_bucket"], + config, + ) + self.bundle_send_to_s3_folder = get_config_variable( + "CONNECTOR_SEND_TO_S3_FOLDER", + ["connector", "send_to_s3_folder"], + config, + default="connectors", + ) + self.bundle_send_to_s3_retention = get_config_variable( + "CONNECTOR_SEND_TO_S3_RETENTION", + ["connector", "send_to_s3_retention"], + config, + isNumber=True, + default=7, + ) + # Cached S3 client for reuse across uploads (lazily initialized) + self._s3_client = None + # Override S3 connection (optional - defaults to OpenCTI's S3) + self.s3_endpoint = get_config_variable( + "S3_ENDPOINT", ["s3", "endpoint"], config + ) + self.s3_port = get_config_variable( + "S3_PORT", ["s3", "port"], config, isNumber=True + ) + self.s3_access_key = get_config_variable( + "S3_ACCESS_KEY", ["s3", "access_key"], config + ) + self.s3_secret_key = get_config_variable( + "S3_SECRET_KEY", ["s3", "secret_key"], config + ) + self.s3_use_ssl = get_config_variable("S3_USE_SSL", ["s3", "use_ssl"], config) + self.s3_bucket_region = get_config_variable( + "S3_BUCKET_REGION", ["s3", "bucket_region"], config + ) self.connect_only_contextual = get_config_variable( "CONNECTOR_ONLY_CONTEXTUAL", ["connector", "only_contextual"], @@ -1446,6 +1976,10 @@ def __init__(self, config: Dict, playbook_compatible=False) -> None: default=self.connector_config["connection"]["pass"], ) + # Initialize S3 config from backend, allow local overrides + if "s3" in self.connector_config: + self._init_s3_config(self.connector_config["s3"]) + # Start ping thread if not self.connect_run_and_terminate: is_run_and_terminate = False @@ -1479,6 +2013,233 @@ def __init__(self, config: Dict, playbook_compatible=False) -> None: # self.listen_stream = None self.listen_queue = None + def _init_s3_config(self, backend_s3_config: Dict) -> None: + """Initialize S3 config using backend values with local overrides. + + Local environment variables or config file settings take precedence + over backend-provided values. If no local override is provided, + the backend value is used. + + :param backend_s3_config: S3 configuration from backend registration + :type backend_s3_config: Dict + """ + # Use local override if set, otherwise use backend value + # Use explicit None checks to support falsy values (0, False, empty string) as valid overrides + self.s3_endpoint = ( + self.s3_endpoint + if self.s3_endpoint is not None + else backend_s3_config.get("endpoint") + ) + self.s3_port = ( + self.s3_port if self.s3_port is not None else backend_s3_config.get("port") + ) + self.s3_access_key = ( + self.s3_access_key + if self.s3_access_key is not None + else backend_s3_config.get("access_key") + ) + self.s3_secret_key = ( + self.s3_secret_key + if self.s3_secret_key is not None + else backend_s3_config.get("secret_key") + ) + self.s3_use_ssl = ( + self.s3_use_ssl + if self.s3_use_ssl is not None + else backend_s3_config.get("use_ssl") + ) + self.s3_bucket_region = ( + self.s3_bucket_region + if self.s3_bucket_region is not None + else backend_s3_config.get("bucket_region") + ) + # Use OpenCTI bucket by default, unless overridden + if self.bundle_send_to_s3_bucket is None: + self.bundle_send_to_s3_bucket = backend_s3_config.get("bucket_name") + + def _get_s3_client(self): + """Create and return an S3 client configured with current settings. + + :return: Configured boto3 S3 client + :rtype: boto3.client + :raises ValueError: If required S3 configuration is missing + """ + # Validate required S3 configuration + missing_config = [] + if not self.s3_endpoint: + missing_config.append("s3_endpoint") + if not self.s3_port: + missing_config.append("s3_port") + if not self.s3_access_key: + missing_config.append("s3_access_key") + if not self.s3_secret_key: + missing_config.append("s3_secret_key") + + if missing_config: + raise ValueError( + f"Missing required S3 configuration: {', '.join(missing_config)}. " + "Ensure S3 credentials are provided via config or OpenCTI backend." + ) + + endpoint_url = ( + f"{'https' if self.s3_use_ssl else 'http'}://" + f"{self.s3_endpoint}:{self.s3_port}" + ) + return boto3.client( + "s3", + endpoint_url=endpoint_url, + aws_access_key_id=self.s3_access_key, + aws_secret_access_key=self.s3_secret_key, + region_name=self.s3_bucket_region, + config=BotoConfig(signature_version="s3v4"), + ) + + def _generate_bundle_filename(self) -> str: + """Generate a unique filename for bundle files. + + :return: Generated filename with connector name, timestamp and .json extension + :rtype: str + """ + return ( + self.connect_name.lower().replace(" ", "_") + + "-" + + time.strftime("%Y%m%d-%H%M%S-") + + str(time.time()) + + ".json" + ) + + def _create_message_bundle( + self, bundle_type: str, bundle: str, entities_types: list, update: bool + ) -> dict: + """Create a message bundle structure for directory or S3 export. + + :param bundle_type: Type of bundle (e.g., "DIRECTORY_BUNDLE", "S3_BUNDLE") + :type bundle_type: str + :param bundle: JSON string of the STIX bundle + :type bundle: str + :param entities_types: List of entity types in the bundle + :type entities_types: list + :param update: Whether this is an update operation + :type update: bool + :return: Message bundle dictionary + :rtype: dict + """ + return { + "bundle_type": bundle_type, + "applicant_id": self.applicant_id, + "connector": { + "id": self.connect_id, + "name": self.connect_name, + "type": self.connect_type, + "scope": self.connect_scope, + "auto": self.connect_auto, + "validate_before_import": self.connect_validate_before_import, + }, + "entities_types": entities_types, + "bundle": json.loads(bundle), + "update": update, + } + + def _send_bundle_to_s3(self, bundle_content: str, bundle_file: str) -> None: + """Upload bundle to S3 bucket. + + :param bundle_content: JSON string content of the bundle to upload + :type bundle_content: str + :param bundle_file: Filename for the bundle in S3 + :type bundle_file: str + :raises Exception: If S3 upload fails + """ + # Validate bucket is configured + if not self.bundle_send_to_s3_bucket: + raise ValueError( + "S3 bucket not configured. Set CONNECTOR_SEND_TO_S3_BUCKET or " + "ensure OpenCTI backend provides bucket configuration." + ) + + # Lazily create and cache the S3 client for reuse across uploads + if self._s3_client is None: + self._s3_client = self._get_s3_client() + s3_client = self._s3_client + + # If folder is empty or "." (or "/" after stripping), upload to root of bucket + # Strip trailing slashes to avoid double slashes in S3 keys + folder = ( + self.bundle_send_to_s3_folder.rstrip("/") + if self.bundle_send_to_s3_folder + else None + ) + if folder and folder != ".": + key = f"{folder}/{bundle_file}" + else: + key = bundle_file + + try: + s3_client.put_object( + Bucket=self.bundle_send_to_s3_bucket, + Key=key, + Body=bundle_content.encode("utf-8"), + ContentType="application/json", + ) + + self.connector_logger.info( + "Bundle uploaded to S3", + {"bucket": self.bundle_send_to_s3_bucket, "key": key}, + ) + + # Handle retention - delete old files + if self.bundle_send_to_s3_retention > 0: + self._cleanup_old_s3_bundles(s3_client) + except Exception as e: + self.connector_logger.error( + "Failed to upload bundle to S3", + {"bucket": self.bundle_send_to_s3_bucket, "key": key, "error": str(e)}, + ) + raise + + def _cleanup_old_s3_bundles(self, s3_client) -> None: + """Remove expired bundles from S3 based on retention policy. + + Only deletes bundles created by this connector (matching connector name prefix) + to avoid deleting bundles from other connectors sharing the same folder. + + :param s3_client: Configured boto3 S3 client + :type s3_client: boto3.client + """ + # Build prefix: folder + connector name prefix + # Strip trailing slashes to avoid double slashes in S3 keys + folder = ( + self.bundle_send_to_s3_folder.rstrip("/") + if self.bundle_send_to_s3_folder + else None + ) + connector_prefix = self.connect_name.lower().replace(" ", "_") + "-" + if folder and folder != ".": + prefix = f"{folder}/{connector_prefix}" + else: + prefix = connector_prefix + + cutoff_time = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta( + days=self.bundle_send_to_s3_retention + ) + + try: + paginator = s3_client.get_paginator("list_objects_v2") + paginate_args = {"Bucket": self.bundle_send_to_s3_bucket, "Prefix": prefix} + for page in paginator.paginate(**paginate_args): + for obj in page.get("Contents", []): + if obj["LastModified"] < cutoff_time: + s3_client.delete_object( + Bucket=self.bundle_send_to_s3_bucket, Key=obj["Key"] + ) + self.connector_logger.debug( + "Deleted expired S3 bundle", + {"key": obj["Key"], "modified": str(obj["LastModified"])}, + ) + except Exception as e: + self.connector_logger.warning( + "Failed to cleanup old S3 bundles", {"error": str(e)} + ) + def stop(self) -> None: """Stop the connector and clean up resources. @@ -1566,9 +2327,12 @@ def get_validate_before_import(self) -> Optional[Union[bool, int, str]]: return self.connect_validate_before_import def set_state(self, state) -> None: - """sets the connector state + """Set the connector state. - :param state: state object + Stores the connector state as a JSON string for persistence across runs. + The state can be retrieved later using get_state(). + + :param state: State object to store, or None to clear the state :type state: Dict or None """ if isinstance(state, Dict): @@ -1577,12 +2341,14 @@ def set_state(self, state) -> None: self.connector_state = None def get_state(self) -> Optional[Dict]: - """get the connector state + """Get the connector state. - :return: returns the current state of the connector if there is any - :rtype: - """ + Retrieves the current connector state that was previously stored. + The state is used to track progress and resume operations across runs. + :return: The current state of the connector, or None if no state exists + :rtype: Optional[Dict] + """ try: if self.connector_state: state = json.loads(self.connector_state) @@ -1622,11 +2388,10 @@ def force_ping(self): self.connector_logger.error("Error pinging the API", {"reason": str(e)}) def next_run_datetime(self, duration_period_in_seconds: Union[int, float]) -> None: - """ - Lets you know what the next run of the scheduler will be in iso datetime format + """Calculate and set the next scheduled run datetime in ISO format. - :param duration_period_in_seconds: Duration in seconds - :return: None + :param duration_period_in_seconds: Duration in seconds until next run + :type duration_period_in_seconds: Union[int, float] """ try: duration_timedelta = datetime.timedelta(seconds=duration_period_in_seconds) @@ -1636,43 +2401,38 @@ def next_run_datetime(self, duration_period_in_seconds: Union[int, float]) -> No "%Y-%m-%dT%H:%M:%SZ" ) self.connector_logger.info( - "[INFO] Schedule next run of connector: ", + "Schedule next run of connector", {"next_run_datetime": self.connector_info.next_run_datetime}, ) - return except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while calculating the next run in datetime", + "An error occurred while calculating the next run in datetime", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) def last_run_datetime(self) -> None: - """ - Lets you know what the last run of the connector the scheduler processed, will be in iso datetime format - - :return: None - """ + """Set the last run datetime to the current UTC time in ISO format.""" try: current_datetime = datetime.datetime.utcnow() # Set last_run_datetime self.connector_info.last_run_datetime = current_datetime.strftime( "%Y-%m-%dT%H:%M:%SZ" ) - return except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while converting the last run in datetime", + "An error occurred while converting the last run in datetime", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) def check_connector_buffering(self) -> bool: - """ - Lets you know if the RabbitMQ queue has exceeded the allowed threshold defined by the connector or not - :return: boolean + """Check if the RabbitMQ queue has exceeded the allowed threshold. + + :return: True if queue size exceeds threshold, False otherwise + :rtype: bool """ try: connector_details = self.api.connector.read(connector_id=self.connector_id) @@ -1684,11 +2444,11 @@ def check_connector_buffering(self) -> bool: queue_messages_size_byte = connector_queue_details["messages_size"] queue_threshold = float(self.connect_queue_threshold) - # Convert queue_messages_size to Mo (decimal) + # Convert queue_messages_size to MB (decimal) queue_messages_size_mo = queue_messages_size_byte / 1000000 self.connector_logger.debug( - "[DEBUG] Connector queue details ...", + "Connector queue details", { "connector_queue_id": connector_queue_id, "queue_threshold": queue_threshold, @@ -1707,7 +2467,7 @@ def check_connector_buffering(self) -> bool: return False else: self.connector_logger.info( - "[INFO] Connector will not run until the queue messages size is reduced under queue threshold" + "Connector will not run until the queue messages size is reduced under queue threshold" ) # Set buffering self.connector_info.buffering = True @@ -1716,13 +2476,13 @@ def check_connector_buffering(self) -> bool: else: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while retrieving connector details" + "An error occurred while retrieving connector details" ) sys.excepthook(*sys.exc_info()) except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while checking the queue size", + "An error occurred while checking the queue size", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1733,17 +2493,17 @@ def schedule_unit( duration_period: Union[int, float, str], time_unit: TimeUnit, ) -> None: - """ - This (deprecated) method is there to manage backward compatibility of intervals on connectors, - allows you to calculate the duration period of connectors in seconds with time_unit and will be - replaced by the "schedule_iso" method. It uses a TimeUnit enum. + """Schedule connector execution with a time unit (deprecated). - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to the connector interval, it can vary depending on the connector - configuration. - :param time_unit: The unit of time for the duration_period. - Enum TimeUnit Valid (YEARS, WEEKS, DAYS, HOURS, MINUTES, SECONDS) - :return: None + This method manages backward compatibility of intervals on connectors. + Use schedule_iso method instead. + + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: The connector interval value + :type duration_period: Union[int, float, str] + :param time_unit: The unit of time (YEARS, WEEKS, DAYS, HOURS, MINUTES, SECONDS) + :type time_unit: TimeUnit """ try: # Calculates the duration period in seconds @@ -1756,7 +2516,7 @@ def schedule_unit( except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An unexpected error occurred during schedule_unit", + "An unexpected error occurred during schedule_unit", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1764,13 +2524,12 @@ def schedule_unit( def schedule_iso( self, message_callback: Callable[[], None], duration_period: str ) -> None: - """ - This method allows you to calculate the duration period of connectors in seconds from ISO 8601 format - and start the scheduler process. + """Schedule connector execution using ISO 8601 duration format. - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to a string in ISO 8601 format "P18Y9W4DT11H9M8S" - :return: None + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: Duration in ISO 8601 format (e.g., "P18Y9W4DT11H9M8S") + :type duration_period: str """ try: if duration_period == "0": @@ -1787,7 +2546,7 @@ def schedule_iso( except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An unexpected error occurred during schedule_iso", + "An unexpected error occurred during schedule_iso", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1798,18 +2557,20 @@ def _schedule_process( message_callback: Callable[[], None], duration_period: Union[int, float], ) -> None: - """ - When scheduling, the function retrieves the details of the connector queue, - and the connector process starts only if the size of the queue messages is less than or - equal to the queue_threshold variable. + """Execute scheduled connector process if queue is not buffering. + + The connector process starts only if the queue messages size is less than + or equal to the queue_threshold variable. - :param scheduler: Scheduler contains a list of all tasks to be started - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to the connector's interval - :return: None + :param scheduler: Scheduler containing tasks to be started + :type scheduler: sched.scheduler + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: The connector's interval in seconds + :type duration_period: Union[int, float] """ try: - self.connector_logger.info("[INFO] Starting schedule") + self.connector_logger.info("Starting schedule") check_connector_buffering = self.check_connector_buffering() if not check_connector_buffering: @@ -1821,7 +2582,7 @@ def _schedule_process( except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while checking the queue size", + "An error occurred while checking the queue size", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1840,19 +2601,21 @@ def _schedule_process( def schedule_process( self, message_callback: Callable[[], None], duration_period: Union[int, float] ) -> None: - """ - This method schedules the execution of a connector process. - If `duration_period' is zero or `self.connect_run_and_terminate' is True, the process will run and terminate. - Otherwise, it schedules the next run based on the interval. + """Schedule the execution of a connector process. + + If duration_period is zero or connect_run_and_terminate is True, + the process will run once and terminate. Otherwise, it schedules + the next run based on the interval. - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to the connector's interval in seconds - :return: None + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: The connector's interval in seconds + :type duration_period: Union[int, float] """ try: # In the case where the duration_period_converted is zero, we consider it to be a run and terminate if self.connect_run_and_terminate or duration_period == 0: - self.connector_logger.info("[INFO] Starting run and terminate") + self.connector_logger.info("Starting run and terminate") # Set run_and_terminate self.connector_info.run_and_terminate = True check_connector_buffering = self.check_connector_buffering() @@ -1863,7 +2626,7 @@ def schedule_process( # Lets you know what is the last run of the connector datetime self.last_run_datetime() - self.connector_logger.info("[INFO] Closing run and terminate") + self.connector_logger.info("Closing run and terminate") self.force_ping() sys.exit(0) else: @@ -1888,14 +2651,14 @@ def schedule_process( except SystemExit: self.connector_logger.info("SystemExit caught, stopping the scheduler") if self.connect_run_and_terminate: - self.connector_logger.info("[INFO] Closing run and terminate") + self.connector_logger.info("Closing run and terminate") self.force_ping() sys.exit(0) except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An unexpected error occurred during schedule", + "An unexpected error occurred during schedule", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1904,12 +2667,17 @@ def listen( self, message_callback: Callable[[Dict], str], ) -> None: - """listen for messages and register callback function + """Listen for messages from the queue and process them via callback. - :param message_callback: callback function to process messages + Starts a listener thread that consumes messages from RabbitMQ or HTTP API + (depending on configured listen protocol) and processes each message + through the provided callback function. This method blocks until the + listener is stopped. + + :param message_callback: Function to process incoming messages. Receives + event data dict and should return a status message string. :type message_callback: Callable[[Dict], str] """ - self.listen_queue = ListenQueue( self, self.opencti_token, @@ -1927,20 +2695,45 @@ def listen( def listen_stream( self, - message_callback, - url=None, - token=None, - verify_ssl=None, - start_timestamp=None, - live_stream_id=None, - listen_delete=None, - no_dependencies=None, - recover_iso_date=None, - with_inferences=None, + message_callback: Callable, + url: Optional[str] = None, + token: Optional[str] = None, + verify_ssl: Optional[bool] = None, + start_timestamp: Optional[str] = None, + live_stream_id: Optional[str] = None, + listen_delete: Optional[bool] = None, + no_dependencies: Optional[bool] = None, + recover_iso_date: Optional[str] = None, + with_inferences: Optional[bool] = None, ) -> ListenStream: - """listen for messages and register callback function - - :param message_callback: callback function to process messages + """Start listening to an OpenCTI event stream. + + Connects to an SSE stream and processes events through the callback. + Parameters default to connector configuration values if not specified. + + :param message_callback: Function to call for each stream event + :type message_callback: Callable + :param url: Base URL for stream (defaults to opencti_url) + :type url: str or None + :param token: Authentication token (defaults to opencti_token) + :type token: str or None + :param verify_ssl: Whether to verify SSL certificates + :type verify_ssl: bool or None + :param start_timestamp: Stream position to start from + :type start_timestamp: str or None + :param live_stream_id: Specific stream ID to connect to + :type live_stream_id: str or None + :param listen_delete: Whether to receive delete events + :type listen_delete: bool or None + :param no_dependencies: Whether to exclude dependencies + :type no_dependencies: bool or None + :param recover_iso_date: ISO date to recover events from + :type recover_iso_date: str or None + :param with_inferences: Whether to include inferred data + :type with_inferences: bool or None + + :return: The started ListenStream thread + :rtype: ListenStream """ # URL if url is None: @@ -2034,9 +2827,16 @@ def get_connector(self) -> OpenCTIConnector: return self.connector def date_now(self) -> str: - """get the current date (UTC) - :return: current datetime for utc + """Get the current UTC datetime in ISO 8601 format. + + Returns the current time with timezone offset notation (+00:00). + + :return: Current UTC datetime as ISO 8601 string (e.g., "2024-01-15T10:30:00+00:00") :rtype: str + + Example: + >>> helper.date_now() + '2024-01-15T10:30:00+00:00' """ return ( datetime.datetime.utcnow() @@ -2045,9 +2845,17 @@ def date_now(self) -> str: ) def date_now_z(self) -> str: - """get the current date (UTC) - :return: current datetime for utc + """Get the current UTC datetime in ISO 8601 format with Z suffix. + + Returns the current time with 'Z' suffix instead of '+00:00'. + This format is commonly used in STIX objects. + + :return: Current UTC datetime as ISO 8601 string (e.g., "2024-01-15T10:30:00Z") :rtype: str + + Example: + >>> helper.date_now_z() + '2024-01-15T10:30:00Z' """ return ( datetime.datetime.utcnow() @@ -2058,21 +2866,53 @@ def date_now_z(self) -> str: # Push Stix2 helper def send_stix2_bundle(self, bundle: str, **kwargs) -> list: - """send a stix2 bundle to the API - - :param work_id: a valid work id - :param draft_id: a draft context to send the bundle to - :param bundle: valid stix2 bundle - :type bundle: - :param entities_types: list of entities, defaults to None + """Send a STIX2 bundle to the OpenCTI platform. + + Processes and sends a STIX2 bundle to OpenCTI via the message queue or API. + The bundle is split into smaller chunks and sent with proper sequencing. + Supports validation workflows, draft mode, and directory export. + + :param bundle: Valid STIX2 bundle as a JSON string + :type bundle: str + :param work_id: Work ID for tracking the import job (default: self.work_id) + :type work_id: str, optional + :param validation_mode: Validation mode - "workbench" or "draft" (default: self.validation_mode) + :type validation_mode: str, optional + :param draft_id: Draft context ID to send the bundle to (default: self.draft_id) + :type draft_id: str, optional + :param entities_types: List of entity types to filter (default: None) :type entities_types: list, optional - :param update: whether to updated data in the database, defaults to False + :param update: Whether to update existing data in the database (default: False) :type update: bool, optional - :param bypass_split: use to prevent splitting of the bundle. This option has been removed since 6.3 and is no longer used. - :type bypass_split: bool, optional - :raises ValueError: if the bundle is empty - :return: list of bundles + :param event_version: Event version for the bundle (default: None) + :type event_version: str, optional + :param bypass_validation: Skip validation workflow (default: False) + :type bypass_validation: bool, optional + :param force_validation: Force validation even if not configured (default: self.force_validation) + :type force_validation: bool, optional + :param entity_id: Entity ID for context (default: None) + :type entity_id: str, optional + :param file_markings: File markings to apply (default: None) + :type file_markings: list, optional + :param file_name: File name for workbench upload (default: None) + :type file_name: str, optional + :param send_to_queue: Whether to send to message queue (default: self.bundle_send_to_queue) + :type send_to_queue: bool, optional + :param cleanup_inconsistent_bundle: Clean up inconsistent bundle data (default: False) + :type cleanup_inconsistent_bundle: bool, optional + :param send_to_directory: Whether to write bundle to directory (default: self.bundle_send_to_directory) + :type send_to_directory: bool, optional + :param send_to_directory_path: Directory path for bundle export (default: self.bundle_send_to_directory_path) + :type send_to_directory_path: str, optional + :param send_to_directory_retention: Days to retain exported files (default: self.bundle_send_to_directory_retention) + :type send_to_directory_retention: int, optional + :param send_to_s3: Whether to upload bundle to S3 (default: self.bundle_send_to_s3) + :type send_to_s3: bool, optional + + :return: List of processed bundle chunks :rtype: list + + :raises ValueError: If the bundle is empty or contains no valid objects """ work_id = kwargs.get("work_id", self.work_id) validation_mode = kwargs.get("validation_mode", self.validation_mode) @@ -2096,6 +2936,7 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list: bundle_send_to_directory_retention = kwargs.get( "send_to_directory_retention", self.bundle_send_to_directory_retention ) + bundle_send_to_s3 = kwargs.get("send_to_s3", self.bundle_send_to_s3) # In case of enrichment ingestion, ensure the sharing if needed if self.enrichment_shared_organizations is not None: @@ -2178,31 +3019,13 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list: "also_queuing": bundle_send_to_queue, }, ) - bundle_file = ( - self.connect_name.lower().replace(" ", "_") - + "-" - + time.strftime("%Y%m%d-%H%M%S-") - + str(time.time()) - + ".json" - ) + bundle_file = self._generate_bundle_filename() write_file = os.path.join( bundle_send_to_directory_path, bundle_file + ".tmp" ) - message_bundle = { - "bundle_type": "DIRECTORY_BUNDLE", - "applicant_id": self.applicant_id, - "connector": { - "id": self.connect_id, - "name": self.connect_name, - "type": self.connect_type, - "scope": self.connect_scope, - "auto": self.connect_auto, - "validate_before_import": self.connect_validate_before_import, - }, - "entities_types": entities_types, - "bundle": json.loads(bundle), - "update": update, - } + message_bundle = self._create_message_bundle( + "DIRECTORY_BUNDLE", bundle, entities_types, update + ) # Maintains the list of files under control if bundle_send_to_directory_retention > 0: # If 0, disable the auto remove current_time = time.time() @@ -2224,6 +3047,23 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list: final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file) os.rename(write_file, final_write_file) + # If S3 setup, upload the bundle to the target S3 bucket + if bundle_send_to_s3 and self.bundle_send_to_s3_bucket is not None: + self.connector_logger.info( + "The connector sending bundle to S3", + { + "connector": self.connect_name, + "bucket": self.bundle_send_to_s3_bucket, + "folder": self.bundle_send_to_s3_folder, + "also_queuing": bundle_send_to_queue, + }, + ) + bundle_file = self._generate_bundle_filename() + message_bundle = self._create_message_bundle( + "S3_BUNDLE", bundle, entities_types, update + ) + self._send_bundle_to_s3(json.dumps(message_bundle), bundle_file) + stix2_splitter = OpenCTIStix2Splitter() (expectations_number, _, bundles) = ( stix2_splitter.split_bundle_with_expectations( @@ -2297,18 +3137,25 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list: return bundles def _send_bundle(self, channel, bundle, **kwargs) -> None: - """send a STIX2 bundle to RabbitMQ to be consumed by workers - - :param channel: RabbitMQ channel - :type channel: callable - :param bundle: valid stix2 bundle - :type bundle: - :param entities_types: list of entity types, defaults to None + """Send a STIX2 bundle to RabbitMQ to be consumed by workers. + + Publishes a bundle message to the configured RabbitMQ exchange with + persistent delivery mode. Retries automatically on delivery failure. + + :param channel: RabbitMQ channel for publishing + :type channel: pika.channel.Channel + :param bundle: Valid STIX2 bundle as a JSON string + :type bundle: str + :param work_id: Work ID for tracking (default: None) + :type work_id: str, optional + :param sequence: Sequence number for bundle ordering (default: 0) + :type sequence: int, optional + :param entities_types: List of entity types to filter (default: None) :type entities_types: list, optional - :param update: whether to update data in the database, defaults to False + :param update: Whether to update existing data in the database (default: False) :type update: bool, optional - :param draft_id: if draft_id is set, bundle must be set in draft context - :type draft_id: + :param draft_id: Draft context ID for the bundle (default: None) + :type draft_id: str, optional """ work_id = kwargs.get("work_id", None) sequence = kwargs.get("sequence", 0) @@ -2361,11 +3208,14 @@ def _send_bundle(self, channel, bundle, **kwargs) -> None: @staticmethod def stix2_deduplicate_objects(items) -> list: - """deduplicate stix2 items + """Deduplicate STIX2 objects by their ID. + + Removes duplicate STIX2 objects from a list, keeping only the first + occurrence of each unique ID. - :param items: valid stix2 items - :type items: - :return: de-duplicated list of items + :param items: List of STIX2 objects to deduplicate + :type items: list + :return: Deduplicated list of STIX2 objects :rtype: list """ @@ -2379,12 +3229,15 @@ def stix2_deduplicate_objects(items) -> list: @staticmethod def stix2_create_bundle(items) -> Optional[str]: - """create a stix2 bundle with items + """Create a STIX2 bundle from a list of objects. - :param items: valid stix2 items - :type items: - :return: JSON of the stix2 bundle - :rtype: + Wraps STIX2 objects in a valid bundle structure with a generated UUID. + Automatically serializes objects if they are STIX2 library instances. + + :param items: List of STIX2 objects (dicts or STIX2 library objects) + :type items: list + :return: JSON string of the STIX2 bundle, or None if items is empty + :rtype: Optional[str] """ # Check if item are native STIX 2 lib @@ -2402,16 +3255,25 @@ def stix2_create_bundle(items) -> Optional[str]: @staticmethod def check_max_tlp(tlp: str, max_tlp: str) -> bool: - """check the allowed TLP levels for a TLP string + """Check if a TLP level is within the allowed maximum TLP level. + + Validates that the given TLP marking is at or below the maximum + allowed TLP level. Useful for filtering data based on sharing + restrictions. - :param tlp: string for TLP level to check + :param tlp: The TLP level to check (e.g., "TLP:GREEN", "TLP:AMBER") :type tlp: str - :param max_tlp: the highest allowed TLP level + :param max_tlp: The highest allowed TLP level for comparison :type max_tlp: str - :return: TLP level in allowed TLPs + :return: True if the TLP level is within the allowed range, False otherwise :rtype: bool - """ + Example: + >>> OpenCTIConnectorHelper.check_max_tlp("TLP:GREEN", "TLP:AMBER") + True + >>> OpenCTIConnectorHelper.check_max_tlp("TLP:RED", "TLP:GREEN") + False + """ if tlp is None or max_tlp is None: return True @@ -2440,47 +3302,84 @@ def check_max_tlp(tlp: str, max_tlp: str) -> bool: return tlp.upper() in allowed_tlps[max_tlp.upper()] @staticmethod - def get_attribute_in_extension(key, object) -> any: + def get_attribute_in_extension(key: str, stix_object: Dict) -> any: + """Get an attribute from OpenCTI STIX extensions. + + Retrieves a value from OpenCTI's custom STIX extension definitions. + Checks both the primary OpenCTI extension and the SDO extension, + falling back to the object's root attributes if not found in extensions. + + :param key: The attribute key to retrieve + :type key: str + :param stix_object: A STIX object dictionary + :type stix_object: Dict + + :return: The attribute value, or None if not found + :rtype: any + + Example: + >>> obj = {"extensions": {"extension-definition--ea279b3e-...": {"score": 85}}} + >>> OpenCTIConnectorHelper.get_attribute_in_extension("score", obj) + 85 + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ][key] elif ( - "extensions" in object + "extensions" in stix_object and "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ][key] - elif key in object and key not in ["type"]: - return object[key] + elif key in stix_object and key not in ["type"]: + return stix_object[key] return None @staticmethod - def get_attribute_in_mitre_extension(key, object) -> any: + def get_attribute_in_mitre_extension(key: str, stix_object: Dict) -> any: + """Get an attribute from MITRE ATT&CK STIX extension. + + Retrieves a value from the MITRE ATT&CK custom STIX extension + definition used for attack patterns and techniques. + + :param key: The attribute key to retrieve + :type key: str + :param stix_object: A STIX object dictionary + :type stix_object: Dict + + :return: The attribute value, or None if not found + :rtype: any + + Example: + >>> obj = {"extensions": {"extension-definition--322b8f77-...": {"x_mitre_version": "1.0"}}} + >>> OpenCTIConnectorHelper.get_attribute_in_mitre_extension("x_mitre_version", obj) + '1.0' + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ][key] return None diff --git a/client-python/pycti/connector/opencti_metric_handler.py b/client-python/pycti/connector/opencti_metric_handler.py index 988ad7cb2b4b..1796980bf4de 100644 --- a/client-python/pycti/connector/opencti_metric_handler.py +++ b/client-python/pycti/connector/opencti_metric_handler.py @@ -1,9 +1,46 @@ +"""OpenCTI Metric Handler module. + +This module provides Prometheus metrics support for OpenCTI connectors, +allowing monitoring of connector performance and health. +""" + from typing import Type, Union from prometheus_client import Counter, Enum, start_http_server class OpenCTIMetricHandler: + """Handler for Prometheus metrics in OpenCTI connectors. + + This class manages Prometheus metrics for monitoring connector behavior, + including bundle sends, records processed, run counts, API pings, and errors. + + When activated, it starts an HTTP server to expose metrics for scraping + by Prometheus or compatible monitoring systems. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param activated: Whether to enable metrics collection and exposure + :type activated: bool + :param namespace: Prometheus metrics namespace prefix + :type namespace: str + :param subsystem: Prometheus metrics subsystem prefix + :type subsystem: str + :param port: Port number for the Prometheus HTTP server + :type port: int + + Example: + >>> handler = OpenCTIMetricHandler( + ... connector_logger=logger, + ... activated=True, + ... namespace="opencti", + ... subsystem="connector", + ... port=9095 + ... ) + >>> handler.inc("bundle_send") + >>> handler.state("running") + """ + def __init__( self, connector_logger, @@ -12,19 +49,18 @@ def __init__( subsystem: str = "", port: int = 9095, ): - """ - Init of OpenCTIMetricHandler class. - - Parameters - ---------- - activated : bool, default False - If True use metrics in client and connectors. - namespace: str, default empty - Namespace for the prometheus metrics. - subsystem: str, default empty - Subsystem for the prometheus metrics. - port : int, default 9095 - Port for prometheus server. + """Initialize the OpenCTIMetricHandler instance. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param activated: Whether to enable metrics (default: False) + :type activated: bool + :param namespace: Prometheus metrics namespace prefix (default: "") + :type namespace: str + :param subsystem: Prometheus metrics subsystem prefix (default: "") + :type subsystem: str + :param port: Port for Prometheus HTTP server (default: 9095) + :type port: int """ self.activated = activated self.connector_logger = connector_logger @@ -46,7 +82,7 @@ def __init__( ), "run_count": Counter( "runs_total", - "Number of run", + "Number of runs", namespace=namespace, subsystem=subsystem, ), @@ -86,22 +122,18 @@ def __init__( def _metric_exists( self, name: str, expected_type: Union[Type[Counter], Type[Enum]] ) -> bool: - """ - Check if a metric exists and has the correct type. + """Check if a metric exists and has the expected type. - If it does not, log an error and return False. + Validates that the named metric is registered and matches the expected + Prometheus metric type. Logs an error if validation fails. - Parameters - ---------- - name : str - Name of the metric to check. - expected_type : Counter or Enum - Expected type of the metric. + :param name: Name of the metric to validate + :type name: str + :param expected_type: Expected Prometheus metric type (Counter or Enum) + :type expected_type: Union[Type[Counter], Type[Enum]] - Returns - ------- - bool - True if the metric exists and is of the correct type else False. + :return: True if metric exists and has correct type, False otherwise + :rtype: bool """ if name not in self._metrics: self.connector_logger.error("Metric does not exist.", {"name": name}) @@ -114,31 +146,54 @@ def _metric_exists( return False return True - def inc(self, name: str, n: int = 1): - """ - Increment the metric (counter) `name` by `n`. - - Parameters - ---------- - name : str - Name of the metric to increment. - n : int, default 1 - Increment the counter by `n`. + def inc(self, name: str, n: int = 1) -> None: + """Increment a counter metric by a specified amount. + + Increments the named counter metric. If metrics are not activated + or the metric does not exist, this method does nothing. + + Available counter metrics: + - bundle_send: Number of bundles sent + - record_send: Number of records sent + - run_count: Number of connector runs + - ping_api_count: Number of API pings + - ping_api_error: Number of API ping errors + - error_count: Total number of errors + - client_error_count: Number of client errors + + :param name: Name of the counter metric to increment + :type name: str + :param n: Amount to increment the counter by (default: 1) + :type n: int + + Example: + >>> handler.inc("bundle_send") + >>> handler.inc("record_send", 10) """ if self.activated: if self._metric_exists(name, Counter): self._metrics[name].inc(n) - def state(self, state: str, name: str = "state"): - """ - Set the state `state` for metric `name`. - - Parameters - ---------- - state : str - State to set. - name : str, default = "state" - Name of the metric to set. + def state(self, state: str, name: str = "state") -> None: + """Set the state of an Enum metric. + + Updates the named Enum metric to the specified state value. + If metrics are not activated or the metric does not exist, + this method does nothing. + + Available states for the default "state" metric: + - idle: Connector is idle + - running: Connector is running + - stopped: Connector is stopped + + :param state: State value to set (must be a valid state for the metric) + :type state: str + :param name: Name of the Enum metric to update (default: "state") + :type name: str + + Example: + >>> handler.state("running") + >>> handler.state("idle", "state") """ if self.activated: if self._metric_exists(name, Enum): diff --git a/client-python/pycti/entities/opencti_attack_pattern.py b/client-python/pycti/entities/opencti_attack_pattern.py index c3934553b176..57da5ee80f20 100644 --- a/client-python/pycti/entities/opencti_attack_pattern.py +++ b/client-python/pycti/entities/opencti_attack_pattern.py @@ -9,10 +9,18 @@ class AttackPattern: """Main AttackPattern class for OpenCTI + Manages MITRE ATT&CK patterns and techniques in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the AttackPattern instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -60,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -160,6 +173,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -276,15 +294,25 @@ def list(self, **kwargs): """List Attack Pattern objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Attack Pattern objects :rtype: list """ @@ -367,15 +395,20 @@ def list(self, **kwargs): result["data"]["attackPatterns"], with_pagination ) - """ - Read a Attack-Pattern object + def read(self, **kwargs): + """Read an Attack Pattern object. - :param id: the id of the Attack-Pattern + :param id: the id of the Attack Pattern + :type id: str :param filters: the filters to apply if no id provided - :return Attack-Pattern object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Attack Pattern object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -411,14 +444,62 @@ def read(self, **kwargs): ) return None - """ - Create a Attack-Pattern object - - :param name: the name of the Attack Pattern - :return Attack-Pattern object - """ - def create(self, **kwargs): + """Create an Attack Pattern object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the attack pattern is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Attack Pattern (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_mitre_platforms: (optional) list of MITRE platforms + :type x_mitre_platforms: list + :param x_mitre_permissions_required: (optional) list of required permissions + :type x_mitre_permissions_required: list + :param x_mitre_detection: (optional) detection guidance + :type x_mitre_detection: str + :param x_mitre_id: (optional) MITRE ATT&CK ID + :type x_mitre_id: str + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Attack Pattern object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -442,6 +523,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Attack-Pattern", {"name": name}) @@ -455,52 +539,56 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "x_mitre_platforms": x_mitre_platforms, - "x_mitre_permissions_required": x_mitre_permissions_required, - "x_mitre_detection": x_mitre_detection, - "x_mitre_id": x_mitre_id, - "killChainPhases": kill_chain_phases, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "x_mitre_platforms": x_mitre_platforms, + "x_mitre_permissions_required": x_mitre_permissions_required, + "x_mitre_detection": x_mitre_detection, + "x_mitre_id": x_mitre_id, + "killChainPhases": kill_chain_phases, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["attackPatternAdd"] ) else: self.opencti.app_logger.error( - "[opencti_attack_pattern] Missing parameters: name and description" + "[opencti_attack_pattern] Missing parameters: name" ) - - """ - Import an Attack-Pattern object from a STIX2 object - - :param stixObject: the Stix-Object Attack-Pattern - :return Attack-Pattern object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Attack Pattern object from a STIX2 object. + + :param stixObject: the STIX2 Attack Pattern object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Attack Pattern object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -567,7 +655,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -646,13 +739,27 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_attack_pattern] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete an Attack Pattern object. + + :param id: the id of the Attack Pattern to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Attack Pattern", {"id": id}) @@ -665,5 +772,7 @@ def delete(self, **kwargs): """ self.opencti.query(query, {"id": id}) else: - self.opencti.app_logger.error("[attack_pattern] Missing parameters: id") + self.opencti.app_logger.error( + "[opencti_attack_pattern] Missing parameters: id" + ) return None diff --git a/client-python/pycti/entities/opencti_campaign.py b/client-python/pycti/entities/opencti_campaign.py index eef61b9b6c32..ad8dee4b4d56 100644 --- a/client-python/pycti/entities/opencti_campaign.py +++ b/client-python/pycti/entities/opencti_campaign.py @@ -9,10 +9,18 @@ class Campaign: """Main Campaign class for OpenCTI + Manages threat campaigns in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Campaign instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -259,15 +267,25 @@ def list(self, **kwargs): """List Campaign objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Campaign objects :rtype: list """ @@ -348,15 +366,20 @@ def list(self, **kwargs): result["data"]["campaigns"], with_pagination ) - """ - Read a Campaign object + def read(self, **kwargs): + """Read a Campaign object. :param id: the id of the Campaign + :type id: str :param filters: the filters to apply if no id provided - :return Campaign object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Campaign object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -392,14 +415,58 @@ def read(self, **kwargs): ) return None - """ - Create a Campaign object - - :param name: the name of the Campaign - :return Campaign object - """ - def create(self, **kwargs): + """Create a Campaign object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the campaign is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Campaign (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param objective: (optional) objective of the campaign + :type objective: str + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Campaign object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -421,6 +488,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Campaign", {"name": name}) @@ -459,23 +529,29 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "x_opencti_stix_ids": x_opencti_stix_ids, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) return self.opencti.process_multiple_fields(result["data"]["campaignAdd"]) else: - self.opencti.app_logger.error( - "[opencti_campaign] Missing parameters: name and description" - ) - - """ - Import a Campaign object from a STIX2 object - - :param stixObject: the Stix-Object Campaign - :return Campaign object - """ + self.opencti.app_logger.error("[opencti_campaign] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Campaign object from a STIX2 object. + + :param stixObject: the STIX2 Campaign object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Campaign object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -497,7 +573,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -560,8 +641,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_campaign] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_capability.py b/client-python/pycti/entities/opencti_capability.py index bd6fbb53e583..0eb28bea62a1 100644 --- a/client-python/pycti/entities/opencti_capability.py +++ b/client-python/pycti/entities/opencti_capability.py @@ -6,9 +6,17 @@ class Capability: See the properties attribute to understand which properties are fetched by default from the graphql queries. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Capability instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id diff --git a/client-python/pycti/entities/opencti_case_incident.py b/client-python/pycti/entities/opencti_case_incident.py index ca9d4eead86a..4472889d7075 100644 --- a/client-python/pycti/entities/opencti_case_incident.py +++ b/client-python/pycti/entities/opencti_case_incident.py @@ -12,9 +12,15 @@ class CaseIncident: Manages incident response cases in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CaseIncident instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -476,6 +482,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Case Incident object. + + :param name: the name of the Case Incident + :type name: str + :param created: the creation date of the Case Incident + :type created: str or datetime.datetime + :return: STIX ID for the Case Incident + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -486,19 +501,29 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Case Incident data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Case Incident + :rtype: str + """ return CaseIncident.generate_id(data["name"], data["created"]) - """ - List Case Incident objects + def list(self, **kwargs): + """List Case Incident objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Case Incident objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Case Incident objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -578,15 +603,16 @@ def list(self, **kwargs): result["data"]["caseIncidents"], with_pagination ) - """ - Read a Case Incident object + def read(self, **kwargs): + """Read a Case Incident object. :param id: the id of the Case Incident + :type id: str :param filters: the filters to apply if no id provided - :return Case Incident object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Case Incident object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -617,16 +643,18 @@ def read(self, **kwargs): else: return None - """ - Read a Case Incident object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Case Incident object by stix_id or name. + + :param stix_id: the STIX ID of the Case Incident + :type stix_id: str + :param name: the name of the Case Incident + :type name: str + :param created: the creation date of the Case Incident + :type created: str + :return: Case Incident object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -649,15 +677,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a case incident already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a case incident already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Case Incident + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if contained, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -685,17 +714,46 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["caseIncidentContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_case_incident] Missing parameters: id or stixObjectOrStixRelationshipId" ) + return None - """ + def create(self, **kwargs): + """ Create a Case Incident object - :param name: the name of the Case Incident - :return Case Incident object - """ - - def create(self, **kwargs): + :param stix_id: (optional) the STIX ID + :param createdBy: (optional) the author ID + :param objects: (optional) list of STIX object IDs contained in the case + :param objectMarking: (optional) list of marking definition IDs + :param objectLabel: (optional) list of label IDs + :param externalReferences: (optional) list of external reference IDs + :param revoked: (optional) whether the case is revoked + :param confidence: (optional) confidence level (0-100) + :param lang: (optional) language + :param created: (optional) creation date + :param modified: (optional) modification date + :param name: the name of the Case Incident (required) + :param description: (optional) description + :param content: (optional) content + :param severity: (optional) severity level + :param priority: (optional) priority level + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :param objectAssignee: (optional) list of assignee IDs + :param objectParticipant: (optional) list of participant IDs + :param objectOrganization: (optional) list of organization IDs + :param response_types: (optional) list of response types + :param x_opencti_workflow_id: (optional) workflow ID + :param x_opencti_modified_at: (optional) custom modification date + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Case Incident object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -720,6 +778,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Case Incident", {"name": name}) @@ -733,54 +794,55 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "content": content, - "severity": severity, - "priority": priority, - "x_opencti_stix_ids": x_opencti_stix_ids, - "response_types": response_types, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "content": content, + "severity": severity, + "priority": priority, + "x_opencti_stix_ids": x_opencti_stix_ids, + "response_types": response_types, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["caseIncidentAdd"] ) else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: name" + "[opencti_case_incident] Missing parameters: name" ) + return None - """ - Add a Stix-Entity object to Case Incident object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Case Incident object (object_refs). :param id: the id of the Case Incident + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -816,26 +878,27 @@ def add_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_incident] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Remove a Stix-Entity object to Case Incident object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Case Incident object (object_refs). :param id: the id of the Case Incident + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to CaseIncident", + "Removing StixObjectOrStixRelationship from CaseIncident", { "stix_object_or_stix_relationship_id": stix_object_or_stix_relationship_id, "id": id, @@ -861,18 +924,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_incident] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Case Incident object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Case Incident object from a STIX2 object. :param stixObject: the Stix-Object Case Incident - :return Case Incident object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Case Incident object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -910,6 +977,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -985,13 +1058,27 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: stixObject" + "[opencti_case_incident] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Case Incident object. + + :param id: the id of the Case Incident to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Case Incident", {"id": id}) diff --git a/client-python/pycti/entities/opencti_case_rfi.py b/client-python/pycti/entities/opencti_case_rfi.py index 22296921b6ea..ac4a98982fc3 100644 --- a/client-python/pycti/entities/opencti_case_rfi.py +++ b/client-python/pycti/entities/opencti_case_rfi.py @@ -12,9 +12,15 @@ class CaseRfi: Manages RFI cases in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CaseRfi instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -474,6 +480,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Case RFI object. + + :param name: the name of the Case RFI + :type name: str + :param created: the creation date of the Case RFI + :type created: str or datetime.datetime + :return: STIX ID for the Case RFI + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -484,19 +499,41 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Case RFI data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Case RFI + :rtype: str + """ return CaseRfi.generate_id(data["name"], data["created"]) - """ - List Case Rfi objects + def list(self, **kwargs): + """List Case RFI objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Case Rfi objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Case RFI objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -574,15 +611,20 @@ def list(self, **kwargs): result["data"]["caseRfis"], with_pagination ) - """ - Read a Case Rfi object + def read(self, **kwargs): + """Read a Case RFI object. - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param filters: the filters to apply if no id provided - :return Case Rfi object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Case RFI object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -613,16 +655,18 @@ def read(self, **kwargs): else: return None - """ - Read a Case Rfi object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Case RFI object by stix_id or name. + + :param stix_id: the STIX ID of the Case RFI + :type stix_id: str + :param name: the name of the Case RFI + :type name: str + :param created: the creation date of the Case RFI + :type created: str + :return: Case RFI object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -645,15 +689,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a case rfi already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a Case RFI already contains a STIX Object or Relationship. - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating if the entity is contained + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -681,17 +726,68 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["caseRfiContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_caseRfi] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_case_rfi] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Case Rfi object - - :param name: the name of the Case Rfi - :return Case Rfi object - """ + return None def create(self, **kwargs): + """Create a Case RFI (Request for Information) object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs contained in the case + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param objectAssignee: (optional) list of assignee IDs + :type objectAssignee: list + :param objectParticipant: (optional) list of participant IDs + :type objectParticipant: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the case is revoked + :type revoked: bool + :param severity: (optional) severity level + :type severity: str + :param priority: (optional) priority level + :type priority: str + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Case RFI (required) + :type name: str + :param content: (optional) content + :type content: str + :param description: (optional) description + :type description: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param information_types: (optional) list of information types + :type information_types: list + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Case RFI object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -716,6 +812,9 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) information_types = kwargs.get("information_types", None) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Case Rfi", {"name": name}) @@ -729,50 +828,51 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "severity": severity, - "priority": priority, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "content": content, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - "information_types": information_types, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "severity": severity, + "priority": priority, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "content": content, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "information_types": information_types, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["caseRfiAdd"]) else: - self.opencti.app_logger.error("[opencti_caseRfi] Missing parameters: name") + self.opencti.app_logger.error("[opencti_case_rfi] Missing parameters: name") + return None - """ - Add a Stix-Entity object to Case Rfi object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Case RFI object (object_refs). - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -806,20 +906,21 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return True else: - self.opencti.app_logger.info( - "[opencti_caseRfi] Missing parameters: id and stixObjectOrStixRelationshipId" + self.opencti.app_logger.error( + "[opencti_case_rfi] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Remove a Stix-Entity object to Case Rfi object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Case RFI object (object_refs). - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -852,18 +953,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseRfi] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_rfi] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Case Rfi object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Case RFI object from a STIX2 object. - :param stixObject: the Stix-Object Case Rfi - :return Case Rfi object + :param stixObject: the STIX2 Case RFI object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Case RFI object + :rtype: dict or None """ - - def import_from_stix2(self, **kwargs): stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -902,6 +1007,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -978,13 +1089,27 @@ def import_from_stix2(self, **kwargs): if "information_types" in stix_object else None ), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), ) else: self.opencti.app_logger.error( - "[opencti_caseRfi] Missing parameters: stixObject" + "[opencti_case_rfi] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Case RFI object. + + :param id: the id of the Case RFI to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Case RFI", {"id": id}) diff --git a/client-python/pycti/entities/opencti_case_rft.py b/client-python/pycti/entities/opencti_case_rft.py index 458386d9d01c..4d85425dd1ca 100644 --- a/client-python/pycti/entities/opencti_case_rft.py +++ b/client-python/pycti/entities/opencti_case_rft.py @@ -12,9 +12,15 @@ class CaseRft: Manages RFT cases in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CaseRft instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -474,6 +480,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Case RFT object. + + :param name: the name of the Case RFT + :type name: str + :param created: the creation date of the Case RFT + :type created: str or datetime.datetime + :return: STIX ID for the Case RFT + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -484,19 +499,41 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Case RFT data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Case RFT + :rtype: str + """ return CaseRft.generate_id(data["name"], data["created"]) - """ - List Case Rft objects + def list(self, **kwargs): + """List Case RFT objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Case Rft objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Case RFT objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -573,15 +610,20 @@ def list(self, **kwargs): result["data"]["caseRfts"], with_pagination ) - """ - Read a Case Rft object + def read(self, **kwargs): + """Read a Case RFT object. - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param filters: the filters to apply if no id provided - :return Case Rft object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Case RFT object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -612,16 +654,20 @@ def read(self, **kwargs): else: return None - """ - Read a Case Rft object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Case RFT object by stix_id or name. + + :param stix_id: the STIX ID of the Case RFT + :type stix_id: str + :param name: the name of the Case RFT + :type name: str + :param created: the creation date + :type created: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Case RFT object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -644,15 +690,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a case rft already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a Case RFT already contains a STIX Object or Relationship. - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating if the entity is contained + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -680,17 +727,68 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["caseRftContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_caseRft] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_case_rft] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Case Rft object - - :param name: the name of the Case Rft - :return Case Rft object - """ + return None def create(self, **kwargs): + """Create a Case RFT (Request for Takedown) object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs contained in the case + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param objectAssignee: (optional) list of assignee IDs + :type objectAssignee: list + :param objectParticipant: (optional) list of participant IDs + :type objectParticipant: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the case is revoked + :type revoked: bool + :param severity: (optional) severity level + :type severity: str + :param priority: (optional) priority level + :type priority: str + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param content: (optional) content + :type content: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Case RFT (required) + :type name: str + :param description: (optional) description + :type description: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param takedown_types: (optional) list of takedown types + :type takedown_types: list + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Case RFT object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -715,6 +813,9 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) takedown_types = kwargs.get("takedown_types", None) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Case Rft", {"name": name}) @@ -728,50 +829,51 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "severity": severity, - "priority": priority, - "content": content, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - "takedown_types": takedown_types, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "severity": severity, + "priority": priority, + "content": content, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "takedown_types": takedown_types, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["caseRftAdd"]) else: - self.opencti.app_logger.error("[opencti_caseRft] Missing parameters: name") + self.opencti.app_logger.error("[opencti_case_rft] Missing parameters: name") + return None - """ - Add a Stix-Entity object to Case Rft object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Case RFT object (object_refs). - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -805,20 +907,21 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return True else: - self.opencti.app_logger.info( - "[opencti_caseRft] Missing parameters: id and stixObjectOrStixRelationshipId" + self.opencti.app_logger.error( + "[opencti_case_rft] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Remove a Stix-Entity object to Case Rft object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Case RFT object (object_refs). - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -851,18 +954,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseRft] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_rft] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Case Rft object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Case RFT object from a STIX2 object. - :param stixObject: the Stix-Object Case Rft - :return Case Rft object + :param stixObject: the STIX2 Case RFT object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Case RFT object + :rtype: dict or None """ - - def import_from_stix2(self, **kwargs): stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -901,6 +1008,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -976,13 +1089,27 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( - "[opencti_caseRft] Missing parameters: stixObject" + "[opencti_case_rft] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Case RFT object. + + :param id: the id of the Case RFT to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Case RFT", {"id": id}) diff --git a/client-python/pycti/entities/opencti_channel.py b/client-python/pycti/entities/opencti_channel.py index a0d8ee72f125..cde73631a6e5 100644 --- a/client-python/pycti/entities/opencti_channel.py +++ b/client-python/pycti/entities/opencti_channel.py @@ -12,9 +12,15 @@ class Channel: Manages communication channels used by threat actors in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Channel instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -213,6 +219,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Channel. + + :param name: the name of the Channel + :type name: str + :return: STIX ID for the Channel + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -221,19 +234,41 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Channel data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Channel + :rtype: str + """ return Channel.generate_id(data["name"]) - """ - List Channel objects + def list(self, **kwargs): + """List Channel objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Channel objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Channel objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -311,15 +346,20 @@ def list(self, **kwargs): result["data"]["channels"], with_pagination ) - """ - Read a Channel object + def read(self, **kwargs): + """Read a Channel object. :param id: the id of the Channel + :type id: str :param filters: the filters to apply if no id provided - :return Channel object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Channel object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -355,14 +395,52 @@ def read(self, **kwargs): ) return None - """ - Create a Channel object - - :param name: the name of the Channel - :return Channel object - """ - def create(self, **kwargs): + """Create a Channel object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the channel is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Channel (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param channel_types: (optional) list of channel types + :type channel_types: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Channel object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -381,6 +459,9 @@ def create(self, **kwargs): granted_refs = kwargs.get("objectOrganization", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Channel", {"name": name}) @@ -394,45 +475,47 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "channel_types": channel_types, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "channel_types": channel_types, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["channelAdd"]) else: - self.opencti.app_logger.error( - "[opencti_channel] Missing parameters: name and description" - ) - - """ - Import an Channel object from a STIX2 object - - :param stixObject: the Stix-Object Channel - :return Channel object - """ + self.opencti.app_logger.error("[opencti_channel] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Channel object from a STIX2 object. + + :param stixObject: the STIX2 Channel object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Channel object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -450,8 +533,13 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - - return self.opencti.channel.create( + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -504,8 +592,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_channel] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_course_of_action.py b/client-python/pycti/entities/opencti_course_of_action.py index 8f00a37fed01..27f99fa3c1dc 100644 --- a/client-python/pycti/entities/opencti_course_of_action.py +++ b/client-python/pycti/entities/opencti_course_of_action.py @@ -12,9 +12,15 @@ class CourseOfAction: Manages courses of action (mitigations) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CourseOfAction instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -149,6 +160,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -219,8 +235,17 @@ def __init__(self, opencti): @staticmethod def generate_id(name, x_mitre_id=None): + """Generate a STIX ID for a Course of Action. + + :param name: The name of the course of action + :type name: str + :param x_mitre_id: Optional MITRE ATT&CK ID + :type x_mitre_id: str or None + :return: STIX ID for the course of action + :rtype: str + """ if x_mitre_id is not None: - data = {"x_mitre_id": x_mitre_id} + data = {"x_mitre_id": x_mitre_id.strip()} else: data = {"name": name.lower().strip()} data = canonicalize(data, utf8=False) @@ -229,19 +254,41 @@ def generate_id(name, x_mitre_id=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from course of action data. + + :param data: Dictionary containing 'name' and optionally 'x_mitre_id' keys + :type data: dict + :return: STIX ID for the course of action + :rtype: str + """ return CourseOfAction.generate_id(data.get("name"), data.get("x_mitre_id")) - """ - List Course-Of-Action objects + def list(self, **kwargs): + """List Course of Action objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Course-Of-Action objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Course of Action objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -321,15 +368,20 @@ def list(self, **kwargs): result["data"]["coursesOfAction"], with_pagination ) - """ - Read a Course-Of-Action object + def read(self, **kwargs): + """Read a Course of Action object. - :param id: the id of the Course-Of-Action + :param id: the id of the Course of Action + :type id: str :param filters: the filters to apply if no id provided - :return Course-Of-Action object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Course of Action object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -367,14 +419,54 @@ def read(self, **kwargs): ) return None - """ - Create a Course Of Action object - - :param name: the name of the Course Of Action - :return Course Of Action object - """ - def create(self, **kwargs): + """Create a Course of Action object. + + :param name: the name of the Course of Action (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the course of action is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_mitre_id: (optional) MITRE ATT&CK ID + :type x_mitre_id: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Course of Action object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -394,6 +486,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Course Of Action", {"name": name}) @@ -430,6 +525,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -438,17 +536,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_course_of_action] Missing parameters: name and description" + "[opencti_course_of_action] Missing parameters: name" ) - - """ - Import an Course-Of-Action object from a STIX2 object - - :param stixObject: the Stix-Object Course-Of-Action - :return Course-Of-Action object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Course of Action object from a STIX2 object. + + :param stixObject: the STIX2 Course of Action object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Course of Action object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -495,7 +598,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -550,8 +658,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_course_of_action] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_data_component.py b/client-python/pycti/entities/opencti_data_component.py index c61b5acced19..4b64109aec20 100644 --- a/client-python/pycti/entities/opencti_data_component.py +++ b/client-python/pycti/entities/opencti_data_component.py @@ -12,9 +12,15 @@ class DataComponent: Manages MITRE ATT&CK data components in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the DataComponent instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -263,6 +269,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Data Component. + + :param name: the name of the Data Component + :type name: str + :return: STIX ID for the Data Component + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -271,19 +284,41 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Data Component data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Data Component + :rtype: str + """ return DataComponent.generate_id(data["name"]) - """ - List Data-Component objects + def list(self, **kwargs): + """List Data Component objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Data-Component objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Data Component objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -363,15 +398,20 @@ def list(self, **kwargs): result["data"]["dataComponents"], with_pagination ) - """ - Read a Data-Component object + def read(self, **kwargs): + """Read a Data Component object. - :param id: the id of the Data-Component + :param id: the id of the Data Component + :type id: str :param filters: the filters to apply if no id provided - :return Data-Component object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Data Component object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -407,14 +447,54 @@ def read(self, **kwargs): ) return None - """ - Create a Data Component object - - :param name: the name of the Data Component - :return Data Component object - """ - def create(self, **kwargs): + """Create a Data Component object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the data component is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Data Component (required) + :type name: str + :param description: (optional) description + :type description: str + :param dataSource: (optional) the data source ID + :type dataSource: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Data Component object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -434,12 +514,12 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Data Component", {"name": name}) - self.opencti.app_logger.info( - "Creating Data Component", {"data": str(kwargs)} - ) query = """ mutation DataComponentAdd($input: DataComponentAddInput!) { dataComponentAdd(input: $input) { @@ -450,48 +530,52 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "dataSource": dataSource, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "dataSource": dataSource, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["dataComponentAdd"] ) else: self.opencti.app_logger.error( - "[opencti_data_component] Missing parameters: name and description" + "[opencti_data_component] Missing parameters: name" ) - - """ - Import an Data-Component object from a STIX2 object - - :param stixObject: the Stix-Object Data-Component - :return Data-Component object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Data Component object from a STIX2 object. + + :param stixObject: the STIX2 Data Component object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Data Component object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -526,8 +610,14 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) - return self.opencti.data_component.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -583,13 +673,28 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( - "[opencti_data_source] Missing parameters: stixObject" + "[opencti_data_component] Missing parameters: stixObject" ) + return None def process_multiple_fields(self, data): + """Process Data Component fields to extract related data source ID. + + :param data: the Data Component data dictionary + :type data: dict + :return: Processed data with dataSourceId field added + :rtype: dict + """ if "dataSource" in data and data["dataSource"] is not None: data["dataSourceId"] = data["dataSource"]["id"] else: diff --git a/client-python/pycti/entities/opencti_data_source.py b/client-python/pycti/entities/opencti_data_source.py index 1fcbbd3f09b1..f1ca67c17c4e 100644 --- a/client-python/pycti/entities/opencti_data_source.py +++ b/client-python/pycti/entities/opencti_data_source.py @@ -12,9 +12,15 @@ class DataSource: Manages MITRE ATT&CK data sources in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the DataSource instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -150,6 +161,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -221,6 +237,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Data Source. + + :param name: the name of the Data Source + :type name: str + :return: STIX ID for the Data Source + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -229,19 +252,41 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Data Source data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Data Source + :rtype: str + """ return DataSource.generate_id(data["name"]) - """ - List Data-Source objects + def list(self, **kwargs): + """List Data Source objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Data-Source objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Data Source objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -319,15 +364,20 @@ def list(self, **kwargs): result["data"]["dataSources"], with_pagination ) - """ - Read a Data-Source object + def read(self, **kwargs): + """Read a Data Source object. - :param id: the id of the Data-Source + :param id: the id of the Data Source + :type id: str :param filters: the filters to apply if no id provided - :return Data-Source object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Data Source object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -363,14 +413,56 @@ def read(self, **kwargs): ) return None - """ - Create a Data Source object - - :param name: the name of the Data Source - :return Data Source object - """ - def create(self, **kwargs): + """Create a Data Source object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the data source is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Data Source (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param platforms: (optional) list of platforms + :type platforms: list + :param collection_layers: (optional) list of collection layers + :type collection_layers: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Data Source object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -391,10 +483,12 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Data Source", {"name": name}) - self.opencti.app_logger.info("Creating Data Source", {"data": str(kwargs)}) query = """ mutation DataSourceAdd($input: DataSourceAddInput!) { dataSourceAdd(input: $input) { @@ -405,47 +499,51 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "x_mitre_platforms": platforms, - "collection_layers": collection_layers, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "x_mitre_platforms": platforms, + "collection_layers": collection_layers, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["dataSourceAdd"]) else: self.opencti.app_logger.error( - "[opencti_data_source] Missing parameters: name and description" + "[opencti_data_source] Missing parameters: name" ) - - """ - Import an Data-Source object from a STIX2 object - - :param stixObject: the Stix-Object Data-Source - :return Data-Source object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Data Source object from a STIX2 object. + + :param stixObject: the STIX2 Data Source object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Data Source object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -482,8 +580,13 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - - return self.opencti.data_source.create( + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -544,8 +647,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_data_source] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_event.py b/client-python/pycti/entities/opencti_event.py index 7c1556dc5139..6cc32ff4fed7 100644 --- a/client-python/pycti/entities/opencti_event.py +++ b/client-python/pycti/entities/opencti_event.py @@ -12,9 +12,15 @@ class Event: Manages security events in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Event instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -261,15 +267,25 @@ def list(self, **kwargs): """List Event objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Event objects :rtype: list """ @@ -350,15 +366,20 @@ def list(self, **kwargs): result["data"]["events"], with_pagination ) - """ - Read a Event object + def read(self, **kwargs): + """Read an Event object. :param id: the id of the Event + :type id: str :param filters: the filters to apply if no id provided - :return Event object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Event object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -394,14 +415,54 @@ def read(self, **kwargs): ) return None - """ - Create a Event object - - :param name: the name of the Event - :return Event object - """ - def create(self, **kwargs): + """Create an Event object. + + :param stix_id: the STIX ID (optional) + :type stix_id: str + :param createdBy: the author ID (optional) + :type createdBy: str + :param objectMarking: list of marking definition IDs (optional) + :type objectMarking: list + :param objectLabel: list of label IDs (optional) + :type objectLabel: list + :param externalReferences: list of external reference IDs (optional) + :type externalReferences: list + :param revoked: whether the event is revoked (optional) + :type revoked: bool + :param confidence: confidence level 0-100 (optional) + :type confidence: int + :param lang: language (optional) + :type lang: str + :param created: creation date (optional) + :type created: str + :param modified: modification date (optional) + :type modified: str + :param name: the name of the Event (required) + :type name: str + :param description: description (optional) + :type description: str + :param aliases: list of aliases (optional) + :type aliases: list + :param start_time: start time of the event (optional) + :type start_time: str + :param stop_time: stop time of the event (optional) + :type stop_time: str + :param event_types: list of event types (optional) + :type event_types: list + :param x_opencti_stix_ids: list of additional STIX IDs (optional) + :type x_opencti_stix_ids: list + :param x_opencti_modified_at: custom modification date (optional) + :type x_opencti_modified_at: str + :param update: whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Event object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -421,6 +482,9 @@ def create(self, **kwargs): x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Event", {"name": name}) @@ -434,46 +498,48 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "start_time": start_time, - "stop_time": stop_time, - "event_types": event_types, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "start_time": start_time, + "stop_time": stop_time, + "event_types": event_types, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["eventAdd"]) else: - self.opencti.app_logger.error( - "[opencti_event] Missing parameters: name and description" - ) + self.opencti.app_logger.error("[opencti_event] Missing parameters: name") + return None - """ - Import an Event object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import an Event object from a STIX2 object. :param stixObject: the Stix-Object Event - :return Event object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Event object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -491,8 +557,13 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - - return self.opencti.event.create( + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -544,8 +615,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_event] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_external_reference.py b/client-python/pycti/entities/opencti_external_reference.py index b8f0b05fe6d6..7211de34c1c7 100644 --- a/client-python/pycti/entities/opencti_external_reference.py +++ b/client-python/pycti/entities/opencti_external_reference.py @@ -14,12 +14,16 @@ class ExternalReference: Manages external references and citations in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the ExternalReference instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = """ id standard_id @@ -66,6 +70,17 @@ def __init__(self, opencti, file): @staticmethod def generate_id(url=None, source_name=None, external_id=None): + """Generate a STIX ID for an External Reference. + + :param url: The URL of the external reference + :type url: str or None + :param source_name: The source name + :type source_name: str or None + :param external_id: The external ID + :type external_id: str or None + :return: STIX ID for the external reference, or None if insufficient parameters + :rtype: str or None + """ if url is not None: data = {"url": url} elif source_name is not None and external_id is not None: @@ -78,20 +93,41 @@ def generate_id(url=None, source_name=None, external_id=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from external reference data. + + :param data: Dictionary containing 'url', 'source_name', or 'external_id' keys + :type data: dict + :return: STIX ID for the external reference + :rtype: str or None + """ return ExternalReference.generate_id( data.get("url"), data.get("source_name"), data.get("external_id") ) - """ - List External-Reference objects + def list(self, **kwargs): + """List External-Reference objects. :param filters: the filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of External-Reference objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of External-Reference objects + :rtype: list + """ filters = kwargs.get("filters", None) first = kwargs.get("first", 500) after = kwargs.get("after", None) @@ -147,7 +183,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["externalReferences"]["pageInfo"]["hasNextPage"]: after = result["data"]["externalReferences"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing External-References", {"after": after} ) result = self.opencti.query( @@ -170,15 +206,16 @@ def list(self, **kwargs): result["data"]["externalReferences"], with_pagination ) - """ - Read a External-Reference object + def read(self, **kwargs): + """Read an External-Reference object. :param id: the id of the External-Reference + :type id: str :param filters: the filters to apply if no id provided - :return External-Reference object - """ - - def read(self, **kwargs): + :type filters: dict + :return: External-Reference object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -210,14 +247,34 @@ def read(self, **kwargs): ) return None - """ - Create a External Reference object - - :param source_name: the source_name of the External Reference - :return External Reference object - """ - def create(self, **kwargs): + """Create an External Reference object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param source_name: the source name of the External Reference (required if no url) + :type source_name: str + :param url: (optional) the URL of the external reference (required if no source_name) + :type url: str + :param external_id: (optional) the external ID + :type external_id: str + :param description: (optional) description + :type description: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: External Reference object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created = kwargs.get("created", None) modified = kwargs.get("modified", None) @@ -227,6 +284,8 @@ def create(self, **kwargs): description = kwargs.get("description", None) x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) if source_name is not None or url is not None: self.opencti.app_logger.info( @@ -243,22 +302,20 @@ def create(self, **kwargs): } """ ) - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "created": created, - "modified": modified, - "source_name": source_name, - "external_id": external_id, - "description": description, - "url": url, - "x_opencti_stix_ids": x_opencti_stix_ids, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "created": created, + "modified": modified, + "source_name": source_name, + "external_id": external_id, + "description": description, + "url": url, + "x_opencti_stix_ids": x_opencti_stix_ids, + "update": update, + "files": files, + "filesMarkings": files_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["externalReferenceAdd"] ) @@ -266,17 +323,30 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_external_reference] Missing parameters: source_name and url" ) - - """ - Upload a file in this External-Reference - - :param id: the Stix-Domain-Object id - :param file_name - :param data - :return void - """ + return None def add_file(self, **kwargs): + """Upload a file in this External-Reference. + + :param id: the External-Reference id + :type id: str + :param file_name: the name of the file to upload + :type file_name: str + :param data: the file data (if None, reads from file_name path) + :type data: bytes or None + :param version: (optional) the file version date + :type version: datetime + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :param mime_type: (optional) MIME type (default: text/plain) + :type mime_type: str + :param no_trigger_import: (optional) don't trigger import (default: False) + :type no_trigger_import: bool + :param embedded: (optional) embed the file (default: False) + :type embedded: bool + :return: File upload result + :rtype: dict or None + """ id = kwargs.get("id", None) file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) @@ -304,14 +374,14 @@ def add_file(self, **kwargs): else: mime_type = magic.from_file(file_name, mime=True) self.opencti.app_logger.info( - "Uploading a file in Stix-Domain-Object", + "Uploading a file in External-Reference", {"file": final_file_name, "id": id}, ) return self.opencti.query( query, { "id": id, - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "fileMarkings": file_markings, "version": version, "noTriggerImport": ( @@ -324,19 +394,20 @@ def add_file(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_domain_object] Missing parameters: id or file_name" + "[opencti_external_reference] Missing parameters: id or file_name" ) return None - """ - Update a External Reference object field + def update_field(self, **kwargs): + """Update an External Reference object field. :param id: the External Reference id + :type id: str :param input: the input of the field - :return The updated External Reference object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated External Reference object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -361,6 +432,12 @@ def update_field(self, **kwargs): return None def delete(self, id): + """Delete an External-Reference object. + + :param id: the id of the External-Reference to delete + :type id: str + :return: None + """ self.opencti.app_logger.info("Deleting External-Reference", {"id": id}) query = """ mutation ExternalReferenceEdit($id: ID!) { @@ -372,6 +449,13 @@ def delete(self, id): self.opencti.query(query, {"id": id}) def list_files(self, **kwargs): + """List files attached to an External-Reference. + + :param id: the id of the External-Reference + :type id: str + :return: List of files + :rtype: list + """ id = kwargs.get("id", None) self.opencti.app_logger.debug("Listing files of External-Reference", {"id": id}) query = """ diff --git a/client-python/pycti/entities/opencti_feedback.py b/client-python/pycti/entities/opencti_feedback.py index b1aa43ad2d41..e8dd49d342fd 100644 --- a/client-python/pycti/entities/opencti_feedback.py +++ b/client-python/pycti/entities/opencti_feedback.py @@ -11,9 +11,15 @@ class Feedback: Manages feedback and analyst assessments in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Feedback instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -436,6 +442,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Feedback object. + + :param name: the name of the Feedback + :type name: str + :return: STIX ID for the Feedback + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -444,19 +457,29 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Feedback data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Feedback + :rtype: str + """ return Feedback.generate_id(data["name"]) - """ - List Feedback objects - + def list(self, **kwargs): + """List Feedback objects. + :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Feedback objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Feedback objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -534,15 +557,16 @@ def list(self, **kwargs): result["data"]["feedbacks"], with_pagination ) - """ - Read a Feedback object + def read(self, **kwargs): + """Read a Feedback object. :param id: the id of the Feedback + :type id: str :param filters: the filters to apply if no id provided - :return Feedback object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Feedback object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -573,18 +597,18 @@ def read(self, **kwargs): else: return None - # TODO: read by stix id or name ? - - """ - Read a Feedback object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Feedback object by stix_id or name. + + :param stix_id: the STIX ID of the Feedback + :type stix_id: str + :param name: the name of the Feedback + :type name: str + :param created: the creation date of the Feedback + :type created: str + :return: Feedback object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -607,15 +631,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a feedback already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a feedback already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Feedback + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if contained, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -645,15 +670,56 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_feedback] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Feedback object - - :param name: the name of the Feedback - :return Feedback object - """ + return None def create(self, **kwargs): + """Create a Feedback object. + + :param stix_id: the STIX ID (optional) + :type stix_id: str + :param createdBy: the author ID (optional) + :type createdBy: str + :param objects: list of STIX object IDs (optional) + :type objects: list + :param objectMarking: list of marking definition IDs (optional) + :type objectMarking: list + :param objectLabel: list of label IDs (optional) + :type objectLabel: list + :param externalReferences: list of external reference IDs (optional) + :type externalReferences: list + :param revoked: whether the feedback is revoked (optional) + :type revoked: bool + :param confidence: confidence level 0-100 (optional) + :type confidence: int + :param lang: language (optional) + :type lang: str + :param created: creation date (optional) + :type created: str + :param modified: modification date (optional) + :type modified: str + :param name: the name of the Feedback (required) + :type name: str + :param description: description (optional) + :type description: str + :param rating: rating value (optional) + :type rating: int + :param x_opencti_stix_ids: list of additional STIX IDs (optional) + :type x_opencti_stix_ids: list + :param objectOrganization: list of organization IDs (optional) + :type objectOrganization: list + :param x_opencti_workflow_id: workflow ID (optional) + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date (optional) + :type x_opencti_modified_at: str + :param update: whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Feedback object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -673,6 +739,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Feedback", {"name": name}) @@ -686,37 +755,46 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "rating": rating, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "rating": rating, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["feedbackAdd"]) else: self.opencti.app_logger.error("[opencti_feedback] Missing parameters: name") + return None def update_field(self, **kwargs): + """Update a field of a Feedback object. + + :param id: the id of the Feedback + :type id: str + :param input: the input containing field(s) to update + :type input: list + :return: Feedback object + :rtype: dict or None + """ self.opencti.app_logger.info("Updating Feedback", {"data": json.dumps(kwargs)}) id = kwargs.get("id", None) input = kwargs.get("input", None) @@ -728,7 +806,7 @@ def update_field(self, **kwargs): id ... on Feedback { name - description + description } } } @@ -744,15 +822,16 @@ def update_field(self, **kwargs): ) return None - """ - Add a Stix-Entity object to Feedback object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Feedback object (object_refs). :param id: the id of the Feedback + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -791,15 +870,16 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Case object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Feedback object (object_refs). - :param id: the id of the Case + :param id: the id of the Feedback + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -836,14 +916,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Feedback object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Feedback object from a STIX2 object. - :param stixObject: the Stix-Object Feedback - :return Feedback object + :param stixObject: the STIX2 Feedback object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Feedback object + :rtype: dict or None """ - - def import_from_stix2(self, **kwargs): stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -865,6 +949,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -920,13 +1010,27 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_feedback] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Feedback object. + + :param id: the id of the Feedback to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Feedback", {"id": id}) diff --git a/client-python/pycti/entities/opencti_group.py b/client-python/pycti/entities/opencti_group.py index dcc21ec3a68d..9a30e606a6e1 100644 --- a/client-python/pycti/entities/opencti_group.py +++ b/client-python/pycti/entities/opencti_group.py @@ -19,9 +19,17 @@ class Group: See the properties attribute to understand what properties are fetched by default from GraphQL queries. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Group instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -327,7 +335,7 @@ def delete(self, **kwargs): id = kwargs.get("id", None) if id is None: self.opencti.admin_logger.error( - "[opencti_group] Cant delete group, missing parameter: id" + "[opencti_group] Cannot delete group, missing parameter: id" ) return None self.opencti.admin_logger.info("Deleting group", {"id": id}) @@ -716,6 +724,13 @@ def delete_allowed_marking(self, **kwargs) -> Optional[Dict]: ) def process_multiple_fields(self, data): + """Process and normalize fields in group data. + + :param data: the group data dictionary to process + :type data: dict + :return: the processed group data with normalized fields + :rtype: dict + """ if "roles" in data: data["roles"] = self.opencti.process_multiple(data["roles"]) data["rolesIds"] = self.opencti.process_multiple_ids(data["roles"]) diff --git a/client-python/pycti/entities/opencti_grouping.py b/client-python/pycti/entities/opencti_grouping.py index 53c17711aa42..5cee5cd4e3e6 100644 --- a/client-python/pycti/entities/opencti_grouping.py +++ b/client-python/pycti/entities/opencti_grouping.py @@ -13,9 +13,15 @@ class Grouping: Manages STIX grouping objects in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Grouping instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -186,7 +192,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -386,7 +392,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -420,6 +426,17 @@ def __init__(self, opencti): @staticmethod def generate_id(name, context, created=None): + """Generate a STIX ID for a Grouping. + + :param name: The name of the grouping + :type name: str + :param context: The grouping context + :type context: str + :param created: Optional creation date + :type created: datetime or str or None + :return: STIX ID for the grouping + :rtype: str + """ name = name.lower().strip() context = context.lower().strip() if isinstance(created, datetime.datetime): @@ -434,19 +451,41 @@ def generate_id(name, context, created=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from grouping data. + + :param data: Dictionary containing 'name', 'context', and 'created' keys + :type data: dict + :return: STIX ID for the grouping + :rtype: str + """ return Grouping.generate_id(data["name"], data["context"], data["created"]) - """ - List Grouping objects + def list(self, **kwargs): + """List Grouping objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Grouping objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Grouping objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -524,15 +563,20 @@ def list(self, **kwargs): result["data"]["groupings"], with_pagination ) - """ - Read a Grouping object + def read(self, **kwargs): + """Read a Grouping object. :param id: the id of the Grouping + :type id: str :param filters: the filters to apply if no id provided - :return Grouping object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withFiles: whether to include files + :type withFiles: bool + :return: Grouping object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -562,17 +606,26 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Read a Grouping object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ + else: + self.opencti.app_logger.error( + "[opencti_grouping] Missing parameters: id or filters" + ) + return None def get_by_stix_id_or_name(self, **kwargs): + """Read a Grouping object by stix_id or name. + + :param stix_id: the STIX ID of the Grouping + :type stix_id: str + :param name: the name of the Grouping + :type name: str + :param context: the context of the Grouping + :type context: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :return: Grouping object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) context = kwargs.get("context", None) @@ -594,15 +647,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a grouping already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a grouping already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Grouping + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -629,15 +683,60 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_grouping] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Grouping object - - :param name: the name of the Grouping - :return Grouping object - """ + return None def create(self, **kwargs): + """Create a Grouping object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the grouping is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param name: the name of the Grouping (required) + :type name: str + :param context: the grouping context (required) + :type context: str + :param content: (optional) content + :type content: str + :param description: (optional) description + :type description: str + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Grouping object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -659,6 +758,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None and context is not None: self.opencti.app_logger.info("Creating Grouping", {"name": name}) @@ -672,49 +774,50 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "context": context, - "content": content, - "description": description, - "x_opencti_aliases": x_opencti_aliases, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "context": context, + "content": content, + "description": description, + "x_opencti_aliases": x_opencti_aliases, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["groupingAdd"]) else: self.opencti.app_logger.error( - "[opencti_grouping] Missing parameters: name and description and context" + "[opencti_grouping] Missing parameters: name and context" ) + return None - """ - Add a Stix-Entity object to Grouping object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Grouping object (object_refs). :param id: the id of the Grouping + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -748,22 +851,23 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Grouping object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Grouping object (object_refs). :param id: the id of the Grouping + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Grouping", + "Removing StixObjectOrStixRelationship from Grouping", {"id": stix_object_or_stix_relationship_id, "grouping": id}, ) query = """ @@ -788,14 +892,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Grouping object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Grouping object from a STIX2 object. :param stixObject: the Stix-Object Grouping - :return Grouping object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Grouping object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -828,7 +936,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -889,8 +1002,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_grouping] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_identity.py b/client-python/pycti/entities/opencti_identity.py index 99601c88bed6..154a5593ca7d 100644 --- a/client-python/pycti/entities/opencti_identity.py +++ b/client-python/pycti/entities/opencti_identity.py @@ -14,9 +14,15 @@ class Identity: Manages individual, organization, and system identities in OpenCTI. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Identity instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -294,16 +300,27 @@ def list(self, **kwargs): """List Identity objects. :param types: the list of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Identity objects :rtype: list """ @@ -370,6 +387,7 @@ def list(self, **kwargs): result = self.opencti.query( query, { + "types": types, "filters": filters, "search": search, "first": first, @@ -386,15 +404,20 @@ def list(self, **kwargs): result["data"]["identities"], with_pagination ) - """ - Read a Identity object + def read(self, **kwargs): + """Read an Identity object. :param id: the id of the Identity + :type id: str :param filters: the filters to apply if no id provided - :return Identity object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Identity object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -430,14 +453,44 @@ def read(self, **kwargs): ) return None - """ - Create a Identity object - - :param name: the name of the Identity - :return Identity object - """ - def create(self, **kwargs): + """Create an Identity object. + + :param type: the type of identity (Organization, Individual, System, etc.) (required) + :param stix_id: (optional) the STIX ID + :param createdBy: (optional) the author ID + :param objectMarking: (optional) list of marking definition IDs + :param objectLabel: (optional) list of label IDs + :param externalReferences: (optional) list of external reference IDs + :param revoked: (optional) whether the identity is revoked + :param confidence: (optional) confidence level (0-100) + :param lang: (optional) language + :param created: (optional) creation date + :param modified: (optional) modification date + :param name: the name of the Identity (required) + :param description: (optional) description + :param contact_information: (optional) contact information + :param roles: (optional) list of roles + :param x_opencti_aliases: (optional) list of aliases + :param security_platform_type: (optional) type of security platform + :param x_opencti_organization_type: (optional) organization type + :param x_opencti_reliability: (optional) reliability level + :param x_opencti_score: (optional) score + :param x_opencti_firstname: (optional) first name for individuals + :param x_opencti_lastname: (optional) last name for individuals + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :param objectOrganization: (optional) list of organization IDs + :param x_opencti_workflow_id: (optional) workflow ID + :param x_opencti_modified_at: (optional) custom modification date + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Identity object + :rtype: dict or None + """ type = kwargs.get("type", None) stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) @@ -465,6 +518,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if type is not None and name is not None: self.opencti.app_logger.info("Creating Identity", {"name": name}) @@ -488,6 +544,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } if type == IdentityTypes.ORGANIZATION.value: query = """ @@ -580,17 +639,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "Missing parameters: type, name and description" + "[opencti_identity] Missing parameters: type and name" ) - - """ - Import an Identity object from a STIX2 object - - :param stixObject: the Stix-Object Identity - :return Identity object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Identity object from a STIX2 object. + + :param stixObject: the STIX2 Identity object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Identity object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -631,12 +695,6 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_score"] = ( self.opencti.get_attribute_in_extension("score", stix_object) ) - if "x_opencti_organization_type" not in stix_object: - stix_object["x_opencti_organization_type"] = ( - self.opencti.get_attribute_in_extension( - "organization_type", stix_object - ) - ) if "x_opencti_firstname" not in stix_object: stix_object["x_opencti_firstname"] = ( self.opencti.get_attribute_in_extension("firstname", stix_object) @@ -661,7 +719,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( type=type, stix_id=stix_object["id"], @@ -754,8 +817,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_identity] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_incident.py b/client-python/pycti/entities/opencti_incident.py index 5b8be76041d4..164146c5d5b1 100644 --- a/client-python/pycti/entities/opencti_incident.py +++ b/client-python/pycti/entities/opencti_incident.py @@ -13,9 +13,15 @@ class Incident: Manages security incidents in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Incident instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -240,6 +246,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for an Incident. + + :param name: The name of the incident + :type name: str + :param created: The creation date of the incident + :type created: str or datetime.datetime + :return: STIX ID for the incident + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -250,19 +265,41 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from incident data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the incident + :rtype: str + """ return Incident.generate_id(data["name"], data["created"]) - """ - List Incident objects + def list(self, **kwargs): + """List Incident objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Incident objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Incident objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -340,15 +377,20 @@ def list(self, **kwargs): result["data"]["incidents"], with_pagination ) - """ - Read a Incident object + def read(self, **kwargs): + """Read an Incident object. :param id: the id of the Incident + :type id: str :param filters: the filters to apply if no id provided - :return Incident object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Incident object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -384,14 +426,64 @@ def read(self, **kwargs): ) return None - """ - Create a Incident object - - :param name: the name of the Incident - :return Incident object - """ - def create(self, **kwargs): + """Create an Incident object. + + :param name: the name of the Incident (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the incident is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param objective: (optional) objective of the incident + :type objective: str + :param incident_type: (optional) type of incident + :type incident_type: str + :param severity: (optional) severity level + :type severity: str + :param source: (optional) source of the incident + :type source: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Incident object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -416,6 +508,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Incident", {"name": name}) @@ -457,21 +552,29 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) return self.opencti.process_multiple_fields(result["data"]["incidentAdd"]) else: - self.opencti.app_logger.error("Missing parameters: name and description") - - """ - Import a Incident object from a STIX2 object - - :param stixObject: the Stix-Object Incident - :return Incident object - """ + self.opencti.app_logger.error("[opencti_incident] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import an Incident object from a STIX2 object. + + :param stixObject: the STIX2 Incident object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Incident object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -493,7 +596,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -563,8 +671,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_incident] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_indicator.py b/client-python/pycti/entities/opencti_indicator.py index 3662372b5962..0a3a08bf56aa 100644 --- a/client-python/pycti/entities/opencti_indicator.py +++ b/client-python/pycti/entities/opencti_indicator.py @@ -14,10 +14,18 @@ class Indicator: """Main Indicator class for OpenCTI + Manages threat indicators and detection patterns in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Indicator instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = INDICATOR_PROPERTIES self.properties_with_files = INDICATOR_PROPERTIES_WITH_FILES @@ -48,22 +56,30 @@ def generate_id_from_data(data): return Indicator.generate_id(data["pattern"]) def list(self, **kwargs): - """List Indicator objects - - The list method accepts the following kwargs: - - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID - or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param list customAttributes: (optional) list of attributes keys to return - :param bool getAll: (optional) switch to return all entries (be careful to use this without any other filters) - :param bool withPagination: (optional) switch to use pagination - :param bool toStix: (optional) get in STIX - + """List Indicator objects. + + :param filters: (optional) the filters to apply + :type filters: dict + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "asc" or "desc" + :type orderMode: str + :param customAttributes: (optional) list of attributes keys to return + :type customAttributes: str + :param getAll: (optional) switch to return all entries (be careful to use this without any other filters) + :type getAll: bool + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :param withFiles: (optional) include files in response + :type withFiles: bool + :param toStix: (optional) get in STIX format + :type toStix: bool :return: List of Indicators :rtype: list """ @@ -141,6 +157,7 @@ def list(self, **kwargs): "after": after, "orderBy": order_by, "orderMode": order_mode, + "toStix": to_stix, }, ) data = self.opencti.process_multiple(result["data"]["indicators"]) @@ -152,20 +169,23 @@ def list(self, **kwargs): ) def read(self, **kwargs): - """Read an Indicator object + """Read an Indicator object. - read can be either used with a known OpenCTI entity `id` or by using a + Read can be either used with a known OpenCTI entity `id` or by using a valid filter to search and return a single Indicator entity or None. - The list method accepts the following kwargs. - Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Group - :param list filters: the filters to apply if no id provided - + :param id: the id of the Indicator + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool :return: Indicator object - :rtype: Indicator + :rtype: dict or None """ id = kwargs.get("id", None) @@ -204,15 +224,72 @@ def read(self, **kwargs): return None def create(self, **kwargs): - """ - Create an Indicator object - - :param str name: the name of the Indicator - :param str pattern: stix indicator pattern - :param str x_opencti_main_observable_type: type of the observable - + """Create an Indicator object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the indicator is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param pattern_type: the pattern type (required) + :type pattern_type: str + :param pattern_version: (optional) the pattern version + :type pattern_version: str + :param pattern: the indicator pattern (required) + :type pattern: str + :param name: the name of the Indicator (defaults to pattern) + :type name: str + :param description: (optional) description + :type description: str + :param indicator_types: (optional) list of indicator types + :type indicator_types: list + :param valid_from: (optional) valid from date + :type valid_from: str + :param valid_until: (optional) valid until date + :type valid_until: str + :param x_opencti_score: (optional) score (default: 50) + :type x_opencti_score: int + :param x_opencti_detection: (optional) detection flag (default: False) + :type x_opencti_detection: bool + :param x_opencti_main_observable_type: the main observable type (required) + :type x_opencti_main_observable_type: str + :param x_mitre_platforms: (optional) list of MITRE platforms + :type x_mitre_platforms: list + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_create_observables: (optional) create observables (default: False) + :type x_opencti_create_observables: bool + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list :return: Indicator object - :rtype: Indicator + :rtype: dict or None """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) @@ -245,6 +322,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if ( name is not None @@ -307,6 +387,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -316,12 +399,15 @@ def create(self, **kwargs): "[opencti_indicator] Missing parameters: " "name or pattern or pattern_type or x_opencti_main_observable_type" ) + return None def update_field(self, **kwargs): """Update an Indicator object field. :param id: the Indicator id + :type id: str :param input: the input of the field + :type input: list :return: Updated indicator object :rtype: dict or None """ @@ -350,19 +436,21 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_domain_object] Cant update indicator field, missing parameters: id and input" + "[opencti_indicator] Cannot update indicator field, missing parameters: id and input" ) return None def add_stix_cyber_observable(self, **kwargs): - """ - Add a Stix-Cyber-Observable object to Indicator object (based-on) + """Add a Stix-Cyber-Observable object to Indicator object (based-on). :param id: the id of the Indicator + :type id: str :param indicator: Indicator object + :type indicator: dict :param stix_cyber_observable_id: the id of the Stix-Observable - - :return: Boolean True if there has been no import error + :type stix_cyber_observable_id: str + :return: True if there has been no import error + :rtype: bool """ id = kwargs.get("id", None) indicator = kwargs.get("indicator", None) @@ -408,15 +496,16 @@ def add_stix_cyber_observable(self, **kwargs): return False def import_from_stix2(self, **kwargs): - """ - Import an Indicator object from a STIX2 object + """Import an Indicator object from a STIX2 object. :param stixObject: the Stix-Object Indicator + :type stixObject: dict :param extras: extra dict - :param bool update: set the update flag on import - + :type extras: dict + :param update: set the update flag on import + :type update: bool :return: Indicator object - :rtype: Indicator + :rtype: dict or None """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) @@ -471,6 +560,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -580,8 +675,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_indicator] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_infrastructure.py b/client-python/pycti/entities/opencti_infrastructure.py index a6749dc65286..088e5e2b243f 100644 --- a/client-python/pycti/entities/opencti_infrastructure.py +++ b/client-python/pycti/entities/opencti_infrastructure.py @@ -9,10 +9,18 @@ class Infrastructure: """Main Infrastructure class for OpenCTI + Manages threat infrastructure (servers, domains, etc.) in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Infrastructure instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -249,6 +257,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for an Infrastructure. + + :param name: The name of the infrastructure + :type name: str + :return: STIX ID for the infrastructure + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -257,23 +272,40 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from infrastructure data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the infrastructure + :rtype: str + """ return Infrastructure.generate_id(data["name"]) def list(self, **kwargs): - """List Infrastructure objects + """List Infrastructure objects. - The list method accepts the following kwargs: - - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID - or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param list customAttributes: (optional) list of attributes keys to return - :param bool getAll: (optional) switch to return all entries (be careful to use this without any other filters) - :param bool withPagination: (optional) switch to use pagination + :param filters: (optional) the filters to apply + :type filters: dict + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "asc" or "desc" + :type orderMode: str + :param customAttributes: (optional) list of attributes keys to return + :type customAttributes: str + :param getAll: (optional) switch to return all entries (be careful to use this without any other filters) + :type getAll: bool + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :param withFiles: (optional) include files in response + :type withFiles: bool + :return: List of Infrastructure objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -334,7 +366,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["infrastructures"]["pageInfo"]["hasNextPage"]: after = result["data"]["infrastructures"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Infrastructures", {"after": after} ) result = self.opencti.query( @@ -357,19 +389,24 @@ def list(self, **kwargs): ) def read(self, **kwargs): - """Read an Infrastructure object + """Read an Infrastructure object. - read can be either used with a known OpenCTI entity `id` or by using a + Read can be either used with a known OpenCTI entity `id` or by using a valid filter to search and return a single Infrastructure entity or None. - The list method accepts the following kwargs. - Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Group - :param list filters: the filters to apply if no id provided + :param id: the id of the Infrastructure + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Infrastructure object + :rtype: dict or None """ - id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -407,14 +444,60 @@ def read(self, **kwargs): ) return None - """ - Create a Infrastructure object - - :param name: the name of the Infrastructure - :return Infrastructure object - """ - def create(self, **kwargs): + """Create an Infrastructure object. + + :param name: the name of the Infrastructure (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the infrastructure is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param infrastructure_types: (optional) list of infrastructure types + :type infrastructure_types: list + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Infrastructure object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -437,6 +520,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Infrastructure", {"name": name}) @@ -476,6 +562,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -484,18 +573,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_infrastructure] Missing parameters: " - "name and infrastructure_pattern and main_observable_type" + "[opencti_infrastructure] Missing parameters: name" ) - - """ - Import an Infrastructure object from a STIX2 object - - :param stixObject: the Stix-Object Infrastructure - :return Infrastructure object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Infrastructure object from a STIX2 object. + + :param stixObject: the STIX2 Infrastructure object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Infrastructure object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -517,6 +610,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -587,8 +686,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_infrastructure] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_intrusion_set.py b/client-python/pycti/entities/opencti_intrusion_set.py index 6c0c9c47b900..35f84c37419a 100644 --- a/client-python/pycti/entities/opencti_intrusion_set.py +++ b/client-python/pycti/entities/opencti_intrusion_set.py @@ -12,9 +12,15 @@ class IntrusionSet: Manages intrusion sets (APT groups) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the IntrusionSet instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -267,15 +273,25 @@ def list(self, **kwargs): """List Intrusion Set objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Intrusion Set objects :rtype: list """ @@ -360,9 +376,13 @@ def read(self, **kwargs): """Read an Intrusion Set object. :param id: the id of the Intrusion Set + :type id: str :param filters: the filters to apply if no id provided + :type filters: dict :param customAttributes: custom attributes to return + :type customAttributes: str :param withFiles: whether to include files + :type withFiles: bool :return: Intrusion Set object :rtype: dict or None """ @@ -404,20 +424,58 @@ def read(self, **kwargs): def create(self, **kwargs): """Create an Intrusion Set object. - :param name: the name of the Intrusion Set + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Intrusion Set (required) + :type name: str :param description: description of the intrusion set + :type description: str :param aliases: list of aliases + :type aliases: list :param first_seen: first seen date + :type first_seen: str :param last_seen: last seen date + :type last_seen: str :param goals: goals of the intrusion set + :type goals: list :param resource_level: resource level + :type resource_level: str :param primary_motivation: primary motivation + :type primary_motivation: str :param secondary_motivations: secondary motivations - :param createdBy: creator identity - :param objectMarking: marking definitions - :param objectLabel: labels - :param externalReferences: external references + :type secondary_motivations: list + :param createdBy: creator identity ID + :type createdBy: str + :param objectMarking: marking definition IDs + :type objectMarking: list + :param objectLabel: label IDs + :type objectLabel: list + :param externalReferences: external reference IDs + :type externalReferences: list + :param objectOrganization: organization IDs + :type objectOrganization: list + :param revoked: whether the intrusion set is revoked + :type revoked: bool + :param confidence: confidence level (0-100) + :type confidence: int + :param lang: language + :type lang: str + :param created: creation date + :type created: str + :param modified: modification date + :type modified: str + :param x_opencti_stix_ids: additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date + :type x_opencti_modified_at: str :param update: whether to update existing intrusion set + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list :return: Intrusion Set object :rtype: dict or None """ @@ -445,6 +503,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Intrusion-Set", {"name": name}) @@ -486,6 +547,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -494,17 +558,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_intrusion_set] Missing parameters: name and description" + "[opencti_intrusion_set] Missing parameters: name" ) - - """ - Import an Intrusion-Set object from a STIX2 object - - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Intrusion Set object from a STIX2 object. + + :param stixObject: the STIX2 Intrusion Set object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Intrusion Set object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -526,7 +595,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -602,8 +676,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_intrusion_set] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_kill_chain_phase.py b/client-python/pycti/entities/opencti_kill_chain_phase.py index 0a2fdb71ec8e..b4c1f725b9aa 100644 --- a/client-python/pycti/entities/opencti_kill_chain_phase.py +++ b/client-python/pycti/entities/opencti_kill_chain_phase.py @@ -11,9 +11,15 @@ class KillChainPhase: Manages kill chain phases (ATT&CK tactics) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the KillChainPhase instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -31,24 +37,50 @@ def __init__(self, opencti): @staticmethod def generate_id(phase_name, kill_chain_name): + """Generate a STIX ID for a Kill Chain Phase. + + :param phase_name: The phase name + :type phase_name: str + :param kill_chain_name: The kill chain name + :type kill_chain_name: str + :return: STIX ID for the kill chain phase + :rtype: str + """ return kill_chain_phase_generate_id( phase_name=phase_name, kill_chain_name=kill_chain_name ) @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from kill chain phase data. + + :param data: Dictionary containing 'phase_name' and 'kill_chain_name' keys + :type data: dict + :return: STIX ID for the kill chain phase + :rtype: str + """ return KillChainPhase.generate_id(data["phase_name"], data["kill_chain_name"]) - """ - List Kill-Chain-Phase objects + def list(self, **kwargs): + """List Kill-Chain-Phase objects. :param filters: the filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Kill-Chain-Phase objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Kill-Chain-Phase objects + :rtype: list + """ filters = kwargs.get("filters", None) first = kwargs.get("first", 500) after = kwargs.get("after", None) @@ -96,15 +128,16 @@ def list(self, **kwargs): result["data"]["killChainPhases"], with_pagination ) - """ - Read a Kill-Chain-Phase object + def read(self, **kwargs): + """Read a Kill-Chain-Phase object. :param id: the id of the Kill-Chain-Phase + :type id: str :param filters: the filters to apply if no id provided - :return Kill-Chain-Phase object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Kill-Chain-Phase object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -136,14 +169,26 @@ def read(self, **kwargs): ) return None - """ - Create a Kill-Chain-Phase object - - :param name: the name of the Kill-Chain-Phase - :return Kill-Chain-Phase object - """ - def create(self, **kwargs): + """Create a Kill-Chain-Phase object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param kill_chain_name: the kill chain name (required) + :type kill_chain_name: str + :param phase_name: the phase name (required) + :type phase_name: str + :param x_opencti_order: (optional) order (default: 0) + :type x_opencti_order: int + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Kill-Chain-Phase object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created = kwargs.get("created", None) modified = kwargs.get("modified", None) @@ -188,16 +233,18 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_kill_chain_phase] Missing parameters: kill_chain_name and phase_name", ) + return None - """ - Update a Kill chain object field + def update_field(self, **kwargs): + """Update a Kill Chain Phase object field. - :param id: the Kill chain id + :param id: the Kill Chain Phase id + :type id: str :param input: the input of the field - :return The updated Kill chain object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated Kill Chain Phase object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -225,11 +272,17 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_kill_chain] Missing parameters: id and key and value" + "[opencti_kill_chain_phase] Missing parameters: id and input" ) return None def delete(self, **kwargs): + """Delete a Kill-Chain-Phase object. + + :param id: the id of the Kill-Chain-Phase to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Kill-Chain-Phase", {"id": id}) diff --git a/client-python/pycti/entities/opencti_label.py b/client-python/pycti/entities/opencti_label.py index d50f6b43caa0..29cfe428cb1a 100644 --- a/client-python/pycti/entities/opencti_label.py +++ b/client-python/pycti/entities/opencti_label.py @@ -12,9 +12,15 @@ class Label: Manages labels and tags in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Label instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -27,21 +33,42 @@ def __init__(self, opencti): @staticmethod def generate_id(value): + """Generate a STIX ID for a Label. + + :param value: The label value + :type value: str + :return: STIX ID for the label + :rtype: str + """ data = {"value": value} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) return "label--" + id - """ - List Label objects + def list(self, **kwargs): + """List Label objects. :param filters: the filters to apply + :type filters: dict + :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Label objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Label objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -114,15 +141,16 @@ def list(self, **kwargs): result["data"]["labels"], with_pagination ) - """ - Read a Label object + def read(self, **kwargs): + """Read a Label object. :param id: the id of the Label + :type id: str :param filters: the filters to apply if no id provided - :return Label object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Label object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -152,15 +180,22 @@ def read(self, **kwargs): ) return None - """ - Create a Label object - - :param value: the value - :param color: the color - :return label object - """ - def create(self, **kwargs): + """Create a Label object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param value: the label value (required) + :type value: str + :param color: (optional) the label color + :type color: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Label object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) value = kwargs.get("value", None) color = kwargs.get("color", None) @@ -195,14 +230,18 @@ def create(self, **kwargs): return self.opencti.process_multiple_fields(result["data"]["labelAdd"]) else: self.opencti.app_logger.error("[opencti_label] Missing parameters: value") - - """ - Read or create a Label - If the user has no rights to create the label, return None - :return The available or created Label object - """ + return None def read_or_create_unchecked(self, **kwargs): + """Read or create a Label. + + If the user has no rights to create the label, return None. + + :param value: the label value + :type value: str + :return: The available or created Label object + :rtype: dict or None + """ value = kwargs.get("value", None) label = self.read( filters={ @@ -218,15 +257,16 @@ def read_or_create_unchecked(self, **kwargs): return None return label - """ - Update a Label object field + def update_field(self, **kwargs): + """Update a Label object field. :param id: the Label id + :type id: str :param input: the input of the field - :return The updated Label object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated Label object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -254,11 +294,17 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_label] Missing parameters: id and key and value" + "[opencti_label] Missing parameters: id and input" ) return None def delete(self, **kwargs): + """Delete a Label object. + + :param id: the id of the Label to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Label", {"id": id}) diff --git a/client-python/pycti/entities/opencti_language.py b/client-python/pycti/entities/opencti_language.py index 965421599842..7c250c6d37cf 100644 --- a/client-python/pycti/entities/opencti_language.py +++ b/client-python/pycti/entities/opencti_language.py @@ -12,9 +12,15 @@ class Language: Manages language entities in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Language instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -173,6 +184,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -241,6 +257,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Language. + + :param name: the name of the Language + :type name: str + :return: STIX ID for the Language + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -249,19 +272,29 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Language data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Language + :rtype: str + """ return Language.generate_id(data["name"]) - """ - List Language objects + def list(self, **kwargs): + """List Language objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Language objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Language objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -339,15 +372,20 @@ def list(self, **kwargs): result["data"]["languages"], with_pagination ) - """ - Read a Language object + def read(self, **kwargs): + """Read a Language object. :param id: the id of the Language + :type id: str :param filters: the filters to apply if no id provided - :return Language object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Language object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -383,14 +421,46 @@ def read(self, **kwargs): ) return None - """ - Create a Language object - - :param name: the name of the Language - :return Language object - """ - def create(self, **kwargs): + """Create a Language object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the language is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language code + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Language (required) + :type name: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Language object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -406,6 +476,8 @@ def create(self, **kwargs): x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Language", {"name": name}) @@ -438,21 +510,28 @@ def create(self, **kwargs): "x_opencti_stix_ids": x_opencti_stix_ids, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["languageAdd"]) else: self.opencti.app_logger.error("[opencti_language] Missing parameters: name") - - """ - Import an Language object from a STIX2 object - - :param stixObject: the Stix-Object Language - :return Language object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Language object from a STIX2 object. + + :param stixObject: the STIX2 Language object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Language object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -471,7 +550,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.language.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -509,8 +588,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), ) else: self.opencti.app_logger.error( "[opencti_language] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_location.py b/client-python/pycti/entities/opencti_location.py index f83234401d41..fc16b77c9d5d 100644 --- a/client-python/pycti/entities/opencti_location.py +++ b/client-python/pycti/entities/opencti_location.py @@ -12,9 +12,15 @@ class Location: Manages geographic locations (countries, cities, regions) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Location instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -233,6 +239,19 @@ def __init__(self, opencti): @staticmethod def generate_id(name, x_opencti_location_type, latitude=None, longitude=None): + """Generate a STIX ID for a Location. + + :param name: The name of the location + :type name: str + :param x_opencti_location_type: The type of location (Country, City, Region, Position) + :type x_opencti_location_type: str + :param latitude: Optional latitude coordinate + :type latitude: float or None + :param longitude: Optional longitude coordinate + :type longitude: float or None + :return: STIX ID for the location + :rtype: str + """ if x_opencti_location_type == "Position": if latitude is not None and longitude is None: data = {"latitude": latitude} @@ -253,6 +272,13 @@ def generate_id(name, x_opencti_location_type, latitude=None, longitude=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from location data. + + :param data: Dictionary containing 'name', 'x_opencti_location_type', and optionally 'latitude'/'longitude' + :type data: dict + :return: STIX ID for the location + :rtype: str + """ return Location.generate_id( data.get("name"), data.get("x_opencti_location_type"), @@ -260,18 +286,34 @@ def generate_id_from_data(data): data.get("longitude"), ) - """ - List Location objects + def list(self, **kwargs): + """List Location objects. - :param types: the list of types + :param types: the list of location types to filter by + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Location objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Location objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -280,6 +322,7 @@ def list(self, **kwargs): order_by = kwargs.get("orderBy", None) order_mode = kwargs.get("orderMode", None) custom_attributes = kwargs.get("customAttributes", None) + get_all = kwargs.get("getAll", False) with_pagination = kwargs.get("withPagination", False) with_files = kwargs.get("withFiles", False) @@ -324,19 +367,47 @@ def list(self, **kwargs): "orderMode": order_mode, }, ) - return self.opencti.process_multiple( - result["data"]["locations"], with_pagination - ) + if get_all: + final_data = [] + data = self.opencti.process_multiple(result["data"]["locations"]) + final_data = final_data + data + while result["data"]["locations"]["pageInfo"]["hasNextPage"]: + after = result["data"]["locations"]["pageInfo"]["endCursor"] + self.opencti.app_logger.debug("Listing Locations", {"after": after}) + result = self.opencti.query( + query, + { + "types": types, + "filters": filters, + "search": search, + "first": first, + "after": after, + "orderBy": order_by, + "orderMode": order_mode, + }, + ) + data = self.opencti.process_multiple(result["data"]["locations"]) + final_data = final_data + data + return final_data + else: + return self.opencti.process_multiple( + result["data"]["locations"], with_pagination + ) - """ - Read a Location object + def read(self, **kwargs): + """Read a Location object. :param id: the id of the Location + :type id: str :param filters: the filters to apply if no id provided - :return Location object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withFiles: whether to include files + :type withFiles: bool + :return: Location object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -372,14 +443,58 @@ def read(self, **kwargs): ) return None - """ - Create a Location object - - :param name: the name of the Location - :return Location object - """ - def create(self, **kwargs): + """Create a Location object. + + :param type: the type of location (Country, City, Region, Position) + :type type: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the location is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param name: the name of the Location (required) + :type name: str + :param description: (optional) description + :type description: str + :param latitude: (optional) latitude coordinate + :type latitude: float + :param longitude: (optional) longitude coordinate + :type longitude: float + :param precision: (optional) precision in meters + :type precision: float + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Location object + :rtype: dict or None + """ type = kwargs.get("type", None) stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) @@ -401,6 +516,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Location", {"name": name}) @@ -439,21 +557,29 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) return self.opencti.process_multiple_fields(result["data"]["locationAdd"]) else: - self.opencti.app_logger.error("Missing parameters: name") + self.opencti.app_logger.error("[opencti_location] Missing parameters: name") + return None - """ - Import an Location object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Location object from a STIX2 object. :param stixObject: the Stix-Object Location - :return Location object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Location object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -467,7 +593,7 @@ def import_from_stix2(self, **kwargs): name = stix_object["region"] else: self.opencti.app_logger.error("[opencti_location] Missing name") - return + return None if "x_opencti_location_type" in stix_object: type = stix_object["x_opencti_location_type"] elif self.opencti.get_attribute_in_extension("type", stix_object) is not None: @@ -499,6 +625,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( type=type, @@ -556,8 +688,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_location] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_malware.py b/client-python/pycti/entities/opencti_malware.py index 7aac182ed385..5f5f406e7cda 100644 --- a/client-python/pycti/entities/opencti_malware.py +++ b/client-python/pycti/entities/opencti_malware.py @@ -12,9 +12,15 @@ class Malware: Manages malware families and variants in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Malware instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -295,15 +301,25 @@ def list(self, **kwargs): """List Malware objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Malware objects :rtype: list """ @@ -389,9 +405,13 @@ def read(self, **kwargs): """Read a Malware object. :param id: the id of the Malware + :type id: str :param filters: the filters to apply if no id provided + :type filters: dict :param customAttributes: custom attributes to return + :type customAttributes: str :param withFiles: whether to include files + :type withFiles: bool :return: Malware object :rtype: dict or None """ @@ -433,23 +453,64 @@ def read(self, **kwargs): def create(self, **kwargs): """Create a Malware object. - :param name: the name of the Malware + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Malware (required) + :type name: str :param description: description of the malware + :type description: str :param aliases: list of aliases + :type aliases: list :param malware_types: types of malware + :type malware_types: list :param is_family: whether this is a malware family + :type is_family: bool :param first_seen: first seen date + :type first_seen: str :param last_seen: last seen date + :type last_seen: str :param architecture_execution_envs: execution environments + :type architecture_execution_envs: list :param implementation_languages: implementation languages + :type implementation_languages: list :param capabilities: malware capabilities + :type capabilities: list :param killChainPhases: kill chain phases + :type killChainPhases: list :param samples: malware samples - :param createdBy: creator identity - :param objectMarking: marking definitions - :param objectLabel: labels - :param externalReferences: external references + :type samples: list + :param createdBy: creator identity ID + :type createdBy: str + :param objectMarking: marking definition IDs + :type objectMarking: list + :param objectLabel: label IDs + :type objectLabel: list + :param externalReferences: external reference IDs + :type externalReferences: list + :param objectOrganization: organization IDs + :type objectOrganization: list + :param revoked: whether the malware is revoked + :type revoked: bool + :param confidence: confidence level (0-100) + :type confidence: int + :param lang: language + :type lang: str + :param created: creation date + :type created: str + :param modified: modification date + :type modified: str + :param x_opencti_stix_ids: additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date + :type x_opencti_modified_at: str :param update: whether to update existing malware + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list :return: Malware object :rtype: dict or None """ @@ -480,6 +541,9 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) samples = kwargs.get("samples", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Malware", {"name": name}) @@ -524,23 +588,29 @@ def create(self, **kwargs): "x_opencti_modified_at": x_opencti_modified_at, "samples": samples, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) return self.opencti.process_multiple_fields(result["data"]["malwareAdd"]) else: - self.opencti.app_logger.error( - "[opencti_malware] Missing parameters: name and description" - ) - - """ - Import an Malware object from a STIX2 object - - :param stixObject: the Stix-Object Malware - :return Malware object - """ + self.opencti.app_logger.error("[opencti_malware] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Malware object from a STIX2 object. + + :param stixObject: the STIX2 Malware object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Malware object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -562,7 +632,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -651,8 +726,16 @@ def import_from_stix2(self, **kwargs): ), samples=(extras["sample_ids"] if "sample_ids" in extras else None), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_malware] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_malware_analysis.py b/client-python/pycti/entities/opencti_malware_analysis.py index 84cacc89ff56..cd266cf5a0f9 100644 --- a/client-python/pycti/entities/opencti_malware_analysis.py +++ b/client-python/pycti/entities/opencti_malware_analysis.py @@ -12,9 +12,15 @@ class MalwareAnalysis: Manages malware analysis reports and results in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the MalwareAnalysis instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -243,6 +249,17 @@ def __init__(self, opencti): @staticmethod def generate_id(result_name, product=None, submitted=None): + """Generate a STIX ID for a Malware Analysis. + + :param result_name: the result name of the analysis + :type result_name: str + :param product: the product that performed the analysis (optional) + :type product: str + :param submitted: the submission date (optional) + :type submitted: str + :return: STIX ID for the Malware Analysis + :rtype: str + """ result_name = result_name.lower().strip() data = {"result_name": result_name, "product": product} if submitted is not None: @@ -253,21 +270,31 @@ def generate_id(result_name, product=None, submitted=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Malware Analysis data. + + :param data: Dictionary containing 'result_name', 'product', and optionally 'submitted' keys + :type data: dict + :return: STIX ID for the Malware Analysis + :rtype: str + """ return MalwareAnalysis.generate_id( data["result_name"], data["product"], data.get("submitted") ) - """ - List Malware analysis objects + def list(self, **kwargs): + """List Malware analysis objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of MalwareAnalysis objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of MalwareAnalysis objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -326,7 +353,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["malwareAnalyses"]["pageInfo"]["hasNextPage"]: after = result["data"]["malwareAnalyses"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Malware analyses", {"after": after} ) result = self.opencti.query( @@ -348,15 +375,16 @@ def list(self, **kwargs): result["data"]["malwareAnalyses"], with_pagination ) - """ - Read a Malware analysis object + def read(self, **kwargs): + """Read a Malware analysis object. :param id: the id of the Malware analysis + :type id: str :param filters: the filters to apply if no id provided - :return Malware analysis object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Malware analysis object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -390,18 +418,82 @@ def read(self, **kwargs): return None else: self.opencti.app_logger.error( - "[opencti_malwareAnalysis] Missing parameters: id or filters" + "[opencti_malware_analysis] Missing parameters: id or filters" ) return None - """ - Create a Malware analysis object - - :param name: the name of the Malware analysis - :return Malware analysis object - """ - def create(self, **kwargs): + """Create a Malware analysis object. + + :param product: the product that performed the analysis (required) + :type product: str + :param result_name: the result name of the analysis (required) + :type result_name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the malware analysis is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param result: (optional) result of the analysis + :type result: str + :param submitted: (optional) submission date + :type submitted: str + :param analysis_started: (optional) analysis start date + :type analysis_started: str + :param analysis_ended: (optional) analysis end date + :type analysis_ended: str + :param version: (optional) version of the analysis + :type version: str + :param configuration_version: (optional) configuration version + :type configuration_version: str + :param analysis_engine_version: (optional) analysis engine version + :type analysis_engine_version: str + :param analysis_definition_version: (optional) analysis definition version + :type analysis_definition_version: str + :param modules: (optional) list of analysis modules + :type modules: list + :param hostVm: (optional) host VM reference ID + :type hostVm: str + :param operatingSystem: (optional) operating system reference ID + :type operatingSystem: str + :param installedSoftware: (optional) list of installed software reference IDs + :type installedSoftware: list + :param sample: (optional) sample reference ID + :type sample: str + :param analysisSco: (optional) list of analysis SCO reference IDs + :type analysisSco: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Malware analysis object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -433,6 +525,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if product is not None and result_name is not None: self.opencti.app_logger.info( @@ -483,6 +578,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -491,17 +589,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_malwareAnalysis] Missing parameters: product and result_name" + "[opencti_malware_analysis] Missing parameters: product and result_name" ) + return None - """ - Import an Malware analysis object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Malware analysis object from a STIX2 object. :param stixObject: the Stix-Object Malware analysis - :return Malware analysis object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Malware analysis object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -523,6 +626,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -626,8 +735,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_malware_analysis] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_marking_definition.py b/client-python/pycti/entities/opencti_marking_definition.py index 01ac52671154..4204625cbdc0 100644 --- a/client-python/pycti/entities/opencti_marking_definition.py +++ b/client-python/pycti/entities/opencti_marking_definition.py @@ -12,9 +12,15 @@ class MarkingDefinition: Manages marking definitions (TLP, statements) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the MarkingDefinition instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -33,6 +39,15 @@ def __init__(self, opencti): @staticmethod def generate_id(definition_type, definition): + """Generate a STIX ID for a Marking Definition. + + :param definition_type: The type of marking (TLP, statement, etc.) + :type definition_type: str + :param definition: The definition value + :type definition: str + :return: STIX ID for the marking definition + :rtype: str + """ # Handle static IDs from OpenCTI if definition_type == "TLP": if definition == "TLP:CLEAR" or definition == "TLP:WHITE": @@ -53,20 +68,37 @@ def generate_id(definition_type, definition): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from marking definition data. + + :param data: Dictionary containing 'definition_type' and 'definition' keys + :type data: dict + :return: STIX ID for the marking definition + :rtype: str + """ return MarkingDefinition.generate_id( data["definition_type"], data["definition"] ) - """ - List Marking-Definition objects + def list(self, **kwargs): + """List Marking-Definition objects. :param filters: the filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Marking-Definition objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Marking-Definition objects + :rtype: list + """ filters = kwargs.get("filters", None) first = kwargs.get("first", 500) after = kwargs.get("after", None) @@ -114,15 +146,16 @@ def list(self, **kwargs): result["data"]["markingDefinitions"], with_pagination ) - """ - Read a Marking-Definition object + def read(self, **kwargs): + """Read a Marking-Definition object. :param id: the id of the Marking-Definition + :type id: str :param filters: the filters to apply if no id provided - :return Marking-Definition object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Marking-Definition object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -154,15 +187,30 @@ def read(self, **kwargs): ) return None - """ - Create a Marking-Definition object - - :param definition_type: the definition_type - :param definition: the definition - :return Marking-Definition object - """ - def create(self, **kwargs): + """Create a Marking-Definition object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param definition_type: the definition type (required) + :type definition_type: str + :param definition: the definition value (required) + :type definition: str + :param x_opencti_order: (optional) order (default: 0) + :type x_opencti_order: int + :param x_opencti_color: (optional) color + :type x_opencti_color: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Marking-Definition object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created = kwargs.get("created", None) modified = kwargs.get("modified", None) @@ -208,16 +256,18 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_marking_definition] Missing parameters: definition and definition_type", ) + return None - """ - Update a Marking definition object field + def update_field(self, **kwargs): + """Update a Marking Definition object field. - :param id: the Marking definition id + :param id: the Marking Definition id + :type id: str :param input: the input of the field - :return The updated Marking definition object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated Marking Definition object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -245,18 +295,20 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_marking_definition] Missing parameters: id and key and value" + "[opencti_marking_definition] Missing parameters: id and input" ) return None - """ - Import an Marking Definition object from a STIX2 object - - :param stixObject: the MarkingDefinition - :return MarkingDefinition object - """ - def import_from_stix2(self, **kwargs): + """Import a Marking Definition object from a STIX2 object. + + :param stixObject: the Stix-Object Marking Definition + :type stixObject: dict + :param update: set the update flag on import + :type update: bool + :return: Marking Definition object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) update = kwargs.get("update", False) if stix_object is not None: @@ -326,7 +378,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("stix_ids", stix_object) ) - return self.opencti.marking_definition.create( + return self.create( stix_id=stix_object["id"], created=stix_object["created"] if "created" in stix_object else None, modified=stix_object["modified"] if "modified" in stix_object else None, @@ -353,8 +405,15 @@ def import_from_stix2(self, **kwargs): self.opencti.app_logger.error( "[opencti_marking_definition] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Marking-Definition object. + + :param id: the id of the Marking-Definition to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Marking-Definition", {"id": id}) diff --git a/client-python/pycti/entities/opencti_narrative.py b/client-python/pycti/entities/opencti_narrative.py index 72df537a029d..222598cae3a9 100644 --- a/client-python/pycti/entities/opencti_narrative.py +++ b/client-python/pycti/entities/opencti_narrative.py @@ -12,9 +12,15 @@ class Narrative: Manages narratives and disinformation campaigns in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Narrative instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -149,6 +160,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -219,6 +235,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Narrative. + + :param name: the name of the Narrative + :type name: str + :return: STIX ID for the Narrative + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -227,19 +250,29 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Narrative data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Narrative + :rtype: str + """ return Narrative.generate_id(data["name"]) - """ - List Narrative objects + def list(self, **kwargs): + """List Narrative objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Narrative objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Narrative objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -317,15 +350,20 @@ def list(self, **kwargs): result["data"]["narratives"], with_pagination ) - """ - Read a Narrative object + def read(self, **kwargs): + """Read a Narrative object. :param id: the id of the Narrative + :type id: str :param filters: the filters to apply if no id provided - :return Narrative object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Narrative object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -361,14 +399,54 @@ def read(self, **kwargs): ) return None - """ - Create a Narrative object - - :param name: the name of the Narrative - :return Narrative object - """ - def create(self, **kwargs): + """Create a Narrative object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the narrative is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Narrative (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param narrative_types: (optional) list of narrative types + :type narrative_types: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Narrative object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -388,6 +466,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Narrative", {"name": name}) @@ -401,46 +482,50 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "narrative_types": narrative_types, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "narrative_types": narrative_types, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["narrativeAdd"]) else: self.opencti.app_logger.error( - "[opencti_narrative] Missing parameters: name and description" + "[opencti_narrative] Missing parameters: name" ) - - """ - Import an Narrative object from a STIX2 object - - :param stixObject: the Stix-Object Narrative - :return Narrative object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Narrative object from a STIX2 object. + + :param stixObject: the STIX2 Narrative object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Narrative object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -462,8 +547,13 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - - return self.opencti.narrative.create( + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -521,8 +611,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_narrative] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_note.py b/client-python/pycti/entities/opencti_note.py index 1bc21774da71..9a40bc30bc2b 100644 --- a/client-python/pycti/entities/opencti_note.py +++ b/client-python/pycti/entities/opencti_note.py @@ -13,9 +13,15 @@ class Note: Manages notes and annotations in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Note instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -208,7 +214,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -221,7 +227,7 @@ def __init__(self, opencti): spec_version created_at updated_at - } + } } } } @@ -272,6 +278,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -425,7 +436,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -438,7 +449,7 @@ def __init__(self, opencti): spec_version created_at updated_at - } + } } } } @@ -459,6 +470,16 @@ def __init__(self, opencti): @staticmethod def generate_id(created, content): + """Generate a STIX ID for a Note. + + :param created: The creation date of the note + :type created: datetime or str or None + :param content: The content of the note (required) + :type content: str + :return: STIX ID for the note + :rtype: str + :raises ValueError: If content is None + """ if content is None: raise ValueError("content is required") if created is not None: @@ -473,19 +494,41 @@ def generate_id(created, content): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from note data. + + :param data: Dictionary containing 'content' and optionally 'created' keys + :type data: dict + :return: STIX ID for the note + :rtype: str + """ return Note.generate_id(data.get("created"), data["content"]) - """ - List Note objects + def list(self, **kwargs): + """List Note objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Note objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Note objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -563,15 +606,20 @@ def list(self, **kwargs): result["data"]["notes"], with_pagination ) - """ - Read a Note object + def read(self, **kwargs): + """Read a Note object. :param id: the id of the Note + :type id: str :param filters: the filters to apply if no id provided - :return Note object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withFiles: whether to include files + :type withFiles: bool + :return: Note object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -601,14 +649,22 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Check if a note already contains a STIX entity - - :return Boolean - """ + else: + self.opencti.app_logger.error( + "[opencti_note] Missing parameters: id or filters" + ) + return None def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a note already contains a STIX entity. + + :param id: the id of the Note + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -638,15 +694,60 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_note] Missing parameters: id or entity_id" ) - - """ - Create a Note object - - :param name: the name of the Note - :return Note object - """ + return None def create(self, **kwargs): + """Create a Note object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the note is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param abstract: (optional) abstract summary + :type abstract: str + :param content: the content of the Note (required) + :type content: str + :param authors: (optional) list of authors + :type authors: list + :param note_types: (optional) list of note types + :type note_types: list + :param likelihood: (optional) likelihood value + :type likelihood: int + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Note object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -668,6 +769,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if content is not None: self.opencti.app_logger.info("Creating Note", {"content": content}) @@ -681,47 +785,48 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "attribute_abstract": abstract, - "content": content, - "authors": authors, - "note_types": note_types, - "likelihood": likelihood, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "attribute_abstract": abstract, + "content": content, + "authors": authors, + "note_types": note_types, + "likelihood": likelihood, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["noteAdd"]) else: self.opencti.app_logger.error("[opencti_note] Missing parameters: content") + return None - """ - Add a Stix-Entity object to Note object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Note object (object_refs). :param id: the id of the Note - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -765,15 +870,16 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Note object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Note object (object_refs). :param id: the id of the Note - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -787,7 +893,7 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): }, ) query = """ - mutation NotetEditRelationDelete($id: ID!, $toId: StixRef!, $relationship_type: String!) { + mutation NoteEditRelationDelete($id: ID!, $toId: StixRef!, $relationship_type: String!) { noteEdit(id: $id) { relationDelete(toId: $toId, relationship_type: $relationship_type) { id @@ -810,14 +916,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Note object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Note object from a STIX2 object. :param stixObject: the Stix-Object Note - :return Note object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Note object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -839,6 +949,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -904,8 +1020,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_note] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_observed_data.py b/client-python/pycti/entities/opencti_observed_data.py index bfc6739a3a56..5670946a70fe 100644 --- a/client-python/pycti/entities/opencti_observed_data.py +++ b/client-python/pycti/entities/opencti_observed_data.py @@ -12,9 +12,15 @@ class ObservedData: Manages observed data and raw intelligence in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the ObservedData instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -457,6 +463,13 @@ def __init__(self, opencti): @staticmethod def generate_id(object_ids): + """Generate a STIX ID for an Observed Data object. + + :param object_ids: list of object IDs contained in the observed data + :type object_ids: list + :return: STIX ID for the Observed Data + :rtype: str + """ data = {"objects": object_ids} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) @@ -464,19 +477,29 @@ def generate_id(object_ids): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Observed Data data. + + :param data: Dictionary containing an 'object_refs' key + :type data: dict + :return: STIX ID for the Observed Data + :rtype: str + """ return ObservedData.generate_id(data["object_refs"]) - """ - List ObservedData objects + def list(self, **kwargs): + """List ObservedData objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of ObservedData objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of ObservedData objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -531,15 +554,16 @@ def list(self, **kwargs): result["data"]["observedDatas"], with_pagination ) - """ - Read a ObservedData object + def read(self, **kwargs): + """Read an ObservedData object. :param id: the id of the ObservedData + :type id: str :param filters: the filters to apply if no id provided - :return ObservedData object - """ - - def read(self, **kwargs): + :type filters: dict + :return: ObservedData object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -569,14 +593,22 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Check if a observedData already contains a STIX entity - - :return Boolean - """ + else: + self.opencti.app_logger.error( + "[opencti_observed_data] Missing parameters: id or filters" + ) + return None def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if an observedData already contains a STIX entity. + + :param id: the id of the ObservedData + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX entity + :type stixObjectOrStixRelationshipId: str + :return: True if contained, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -604,17 +636,58 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["observedDataContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_observedData] Missing parameters: id or entity_id" + "[opencti_observed_data] Missing parameters: id or entity_id" ) - - """ - Create a ObservedData object - - :param name: the name of the ObservedData - :return ObservedData object - """ + return None def create(self, **kwargs): + """Create an ObservedData object. + + :param stix_id: the STIX ID (optional) + :type stix_id: str + :param createdBy: the author ID (optional) + :type createdBy: str + :param objects: list of STIX object IDs (required) + :type objects: list + :param objectMarking: list of marking definition IDs (optional) + :type objectMarking: list + :param objectLabel: list of label IDs (optional) + :type objectLabel: list + :param externalReferences: list of external reference IDs (optional) + :type externalReferences: list + :param revoked: whether the observed data is revoked (optional) + :type revoked: bool + :param confidence: confidence level 0-100 (optional) + :type confidence: int + :param lang: language (optional) + :type lang: str + :param created: creation date (optional) + :type created: str + :param modified: modification date (optional) + :type modified: str + :param first_observed: the first observed datetime (required) + :type first_observed: str + :param last_observed: the last observed datetime (required) + :type last_observed: str + :param number_observed: number of times observed (optional) + :type number_observed: int + :param x_opencti_stix_ids: list of additional STIX IDs (optional) + :type x_opencti_stix_ids: list + :param objectOrganization: list of organization IDs (optional) + :type objectOrganization: list + :param x_opencti_workflow_id: workflow ID (optional) + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date (optional) + :type x_opencti_modified_at: str + :param update: whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: ObservedData object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -634,6 +707,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if ( first_observed is not None @@ -641,6 +717,7 @@ def create(self, **kwargs): and objects is not None ): self.opencti.app_logger.info("Creating ObservedData") + query = """ mutation ObservedDataAdd($input: ObservedDataAddInput!) { observedDataAdd(input: $input) { @@ -651,50 +728,51 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "first_observed": first_observed, - "last_observed": last_observed, - "number_observed": number_observed, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "first_observed": first_observed, + "last_observed": last_observed, + "number_observed": number_observed, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["observedDataAdd"] ) else: self.opencti.app_logger.error( - "[opencti_observedData] Missing parameters: " + "[opencti_observed_data] Missing parameters: " "first_observed, last_observed or objects" ) + return None - """ - Add a Stix-Core-Object or stix_relationship to ObservedData object (object) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Core-Object or stix_relationship to ObservedData object (object). :param id: the id of the ObservedData - :param entity_id: the id of the Stix-Core-Object or stix_relationship - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Core-Object or stix_relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -734,27 +812,28 @@ def add_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_observedData] Missing parameters: " + "[opencti_observed_data] Missing parameters: " "id and stix_object_or_stix_relationship_id" ) return False - """ - Remove a Stix-Core-Object or stix_relationship to Observed-Data object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Core-Object or stix_relationship from Observed-Data object. :param id: the id of the Observed-Data - :param entity_id: the id of the Stix-Core-Object or stix_relationship - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Core-Object or stix_relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Observed-Data", + "Removing StixObjectOrStixRelationship from Observed-Data", { "id": id, "stixObjectOrStixRelationshipId": stix_object_or_stix_relationship_id, @@ -784,14 +863,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a ObservedData object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import an ObservedData object from a STIX2 object. :param stixObject: the Stix-Object ObservedData - :return ObservedData object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: ObservedData object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -846,6 +929,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) observed_data_result = self.create( stix_id=stix_object["id"], @@ -909,6 +998,13 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) return observed_data_result @@ -916,3 +1012,4 @@ def import_from_stix2(self, **kwargs): self.opencti.app_logger.error( "[opencti_observed_data] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_opinion.py b/client-python/pycti/entities/opencti_opinion.py index ac83bc60622c..3b24cb6f7f58 100644 --- a/client-python/pycti/entities/opencti_opinion.py +++ b/client-python/pycti/entities/opencti_opinion.py @@ -1,4 +1,5 @@ # coding: utf-8 + import datetime import json import uuid @@ -12,9 +13,15 @@ class Opinion: Manages analyst opinions and assessments in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Opinion instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +69,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -195,7 +207,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -208,7 +220,7 @@ def __init__(self, opencti): spec_version created_at updated_at - } + } } } } @@ -229,6 +241,16 @@ def __init__(self, opencti): @staticmethod def generate_id(created, opinion): + """Generate a STIX ID for an Opinion. + + :param created: The creation date of the opinion + :type created: datetime or str or None + :param opinion: The opinion value (required) + :type opinion: str + :return: STIX ID for the opinion + :rtype: str + :raises ValueError: If opinion is None + """ if opinion is None: raise ValueError("opinion is required") if created is not None: @@ -243,19 +265,39 @@ def generate_id(created, opinion): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from opinion data. + + :param data: Dictionary containing 'opinion' and optionally 'created' keys + :type data: dict + :return: STIX ID for the opinion + :rtype: str + """ return Opinion.generate_id(data.get("created"), data["opinion"]) - """ - List Opinion objects + def list(self, **kwargs): + """List Opinion objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Opinion objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Opinion objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -328,15 +370,18 @@ def list(self, **kwargs): result["data"]["opinions"], with_pagination ) - """ - Read a Opinion object + def read(self, **kwargs): + """Read an Opinion object. :param id: the id of the Opinion + :type id: str :param filters: the filters to apply if no id provided - :return Opinion object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :return: Opinion object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -365,14 +410,22 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Check if a opinion already contains a STIX entity - - :return Boolean - """ + else: + self.opencti.app_logger.error( + "[opencti_opinion] Missing parameters: id or filters" + ) + return None def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if an opinion already contains a STIX entity. + + :param id: the id of the Opinion + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -400,17 +453,56 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["opinionContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_opinion] Missing parameters: id or entity_id" + "[opencti_opinion] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Opinion object - - :param name: the name of the Opinion - :return Opinion object - """ + return None def create(self, **kwargs): + """Create an Opinion object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the opinion is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param explanation: (optional) explanation text + :type explanation: str + :param authors: (optional) list of authors + :type authors: list + :param opinion: the opinion value (required) + :type opinion: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Opinion object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -427,8 +519,11 @@ def create(self, **kwargs): opinion = kwargs.get("opinion", None) x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) granted_refs = kwargs.get("objectOrganization", None) + x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) if opinion is not None: self.opencti.app_logger.info("Creating Opinion", {"opinion": opinion}) @@ -442,46 +537,47 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "explanation": explanation, - "authors": authors, - "opinion": opinion, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "explanation": explanation, + "authors": authors, + "opinion": opinion, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["opinionAdd"]) else: self.opencti.app_logger.error( - "[opencti_opinion] Missing parameters: content" + "[opencti_opinion] Missing parameters: opinion" ) + return None - """ - Add a Stix-Entity object to Opinion object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Opinion object (object_refs). :param id: the id of the Opinion - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -525,22 +621,23 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object from Opinion object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Opinion object (object_refs). :param id: the id of the Opinion - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Opinion", + "Removing StixObjectOrStixRelationship from Opinion", { "id": id, "stixObjectOrStixRelationshipId": stix_object_or_stix_relationship_id, @@ -566,18 +663,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_opinion] Missing parameters: id and entity_id" + "[opencti_opinion] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Opinion object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import an Opinion object from a STIX2 object. :param stixObject: the Stix-Object Opinion - :return Opinion object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Opinion object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -595,6 +696,10 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "x_opencti_workflow_id" not in stix_object: + stix_object["x_opencti_workflow_id"] = ( + self.opencti.get_attribute_in_extension("workflow_id", stix_object) + ) return self.create( stix_id=stix_object["id"], @@ -638,6 +743,11 @@ def import_from_stix2(self, **kwargs): if "x_opencti_modified_at" in stix_object else None ), + x_opencti_workflow_id=( + stix_object["x_opencti_workflow_id"] + if "x_opencti_workflow_id" in stix_object + else None + ), opinion=stix_object["opinion"] if "opinion" in stix_object else None, objectOrganization=( stix_object["x_opencti_granted_refs"] @@ -645,8 +755,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), ) else: self.opencti.app_logger.error( "[opencti_opinion] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_report.py b/client-python/pycti/entities/opencti_report.py index c243d324820b..86f7c9bd451c 100644 --- a/client-python/pycti/entities/opencti_report.py +++ b/client-python/pycti/entities/opencti_report.py @@ -14,9 +14,15 @@ class Report: Manages threat intelligence reports in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Report instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -210,7 +216,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -432,7 +438,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -466,6 +472,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, published): + """Generate a STIX ID for a Report. + + :param name: The name of the report + :type name: str + :param published: The published date of the report + :type published: str or datetime.datetime + :return: STIX ID for the report + :rtype: str + """ name = name.lower().strip() if isinstance(published, datetime.datetime): published = published.isoformat() @@ -476,6 +491,15 @@ def generate_id(name, published): @staticmethod def generate_fixed_fake_id(name, published=None): + """Generate a fixed fake STIX ID for a Report (used for testing). + + :param name: The name of the report + :type name: str + :param published: (optional) The published date of the report + :type published: str or datetime.datetime or None + :return: STIX ID for the report + :rtype: str + """ name = name.lower().strip() if isinstance(published, datetime.datetime): published = published.isoformat() @@ -489,19 +513,41 @@ def generate_fixed_fake_id(name, published=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from report data. + + :param data: Dictionary containing 'name' and 'published' keys + :type data: dict + :return: STIX ID for the report + :rtype: str + """ return Report.generate_id(data["name"], data["published"]) - """ - List Report objects + def list(self, **kwargs): + """List Report objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Report objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Report objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -515,7 +561,7 @@ def list(self, **kwargs): self.opencti.app_logger.info( "Listing Reports with filters", - {"filters": json.dumps(filters), "with_files:": with_files}, + {"filters": json.dumps(filters), "with_files": with_files}, ) query = ( """ @@ -580,15 +626,20 @@ def list(self, **kwargs): result["data"]["reports"], with_pagination ) - """ - Read a Report object + def read(self, **kwargs): + """Read a Report object. :param id: the id of the Report + :type id: str :param filters: the filters to apply if no id provided - :return Report object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Report object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -620,17 +671,26 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Read a Report object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ + else: + self.opencti.app_logger.error( + "[opencti_report] Missing parameters: id or filters" + ) + return None def get_by_stix_id_or_name(self, **kwargs): + """Read a Report object by stix_id or name. + + :param stix_id: the STIX ID of the Report + :type stix_id: str + :param name: the name of the Report + :type name: str + :param published: the published date of the Report + :type published: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Report object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) published = kwargs.get("published", None) @@ -653,15 +713,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a report already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a report already contains a STIX object or relationship. :param id: the id of the Report - :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX object or relationship + :type stixObjectOrStixRelationshipId: str + :return: True if the report contains the entity, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -691,15 +752,43 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_report] Missing parameters: id or stixObjectOrStixRelationshipId", ) - - """ - Create a Report object - - :param name: the name of the Report - :return Report object - """ + return None def create(self, **kwargs): + """Create a Report object. + + :param stix_id: (optional) the STIX ID of the Report + :param createdBy: (optional) the author ID + :param objects: (optional) list of STIX object IDs contained in the report + :param objectMarking: (optional) list of marking definition IDs + :param objectAssignee: (optional) list of assignee IDs + :param objectParticipant: (optional) list of participant IDs + :param objectLabel: (optional) list of label IDs + :param externalReferences: (optional) list of external reference IDs + :param revoked: (optional) whether the report is revoked + :param confidence: (optional) confidence level (0-100) + :param lang: (optional) language of the report + :param created: (optional) creation date + :param modified: (optional) modification date + :param name: the name of the Report (required) + :param description: (optional) description of the report + :param content: (optional) content of the report + :param report_types: (optional) list of report types + :param published: the publication date (required) + :param x_opencti_reliability: (optional) reliability level + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :param objectOrganization: (optional) list of organization IDs + :param x_opencti_workflow_id: (optional) workflow ID + :param x_opencti_modified_at: (optional) custom modification date + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Report object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -724,6 +813,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None and published is not None: self.opencti.app_logger.info("Creating Report", {"name": name}) @@ -737,53 +829,53 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "content": content, - "report_types": report_types, - "published": published, - "x_opencti_reliability": x_opencti_reliability, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "content": content, + "report_types": report_types, + "published": published, + "x_opencti_reliability": x_opencti_reliability, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["reportAdd"]) else: self.opencti.app_logger.error( - "[opencti_report] " - "Missing parameters: name and description and published and report_class" + "[opencti_report] Missing parameters: name and published" ) + return None - """ - Add a Stix-Entity object to Report object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a STIX object or relationship to Report object (object_refs). :param id: the id of the Report - :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX object or relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -822,22 +914,23 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Report object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a STIX object or relationship from Report object (object_refs). :param id: the id of the Report - :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX object or relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Report", + "Removing StixObjectOrStixRelationship from Report", { "id": id, "stixObjectOrStixRelationshipId": stix_object_or_stix_relationship_id, @@ -867,14 +960,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Report object from a STIX2 object - - :param stixObject: the Stix-Object Report - :return Report object - """ - def import_from_stix2(self, **kwargs): + """Import a Report object from a STIX2 object. + + :param stixObject: the STIX2 Report object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, object_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Report object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -916,6 +1013,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -997,8 +1100,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_report] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_role.py b/client-python/pycti/entities/opencti_role.py index 6e0878e596dd..9981d13122d0 100644 --- a/client-python/pycti/entities/opencti_role.py +++ b/client-python/pycti/entities/opencti_role.py @@ -10,9 +10,17 @@ class Role: Check the properties attribute of the class to understand what default properties are fetched. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Role instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -317,7 +325,7 @@ def add_capability(self, **kwargs) -> Optional[Dict]: capability_id = kwargs.get("capability_id", None) if id is None or capability_id is None: - self.opencti.admin_logger( + self.opencti.admin_logger.error( "[opencti_role] Missing parameters: id and capability_id" ) return None @@ -405,6 +413,13 @@ def delete_capability(self, **kwargs) -> Optional[Dict]: ) def process_multiple_fields(self, data): + """Process and normalize fields in role data. + + :param data: the role data dictionary to process + :type data: dict + :return: the processed role data with normalized fields + :rtype: dict + """ if "capabilities" in data: data["capabilities"] = self.opencti.process_multiple(data["capabilities"]) data["capabilitiesIds"] = self.opencti.process_multiple_ids( diff --git a/client-python/pycti/entities/opencti_security_coverage.py b/client-python/pycti/entities/opencti_security_coverage.py index a03638a6e2d9..67686bbeef26 100644 --- a/client-python/pycti/entities/opencti_security_coverage.py +++ b/client-python/pycti/entities/opencti_security_coverage.py @@ -7,7 +7,20 @@ class SecurityCoverage: + """Main SecurityCoverage class for OpenCTI + + Manages security coverage entities in the OpenCTI platform. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient + """ + def __init__(self, opencti): + """Initialize the SecurityCoverage instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -39,6 +52,13 @@ def __init__(self, opencti): @staticmethod def generate_id(covered_ref): + """Generate a STIX ID for a Security Coverage. + + :param covered_ref: The reference to the covered object + :type covered_ref: str + :return: STIX ID for the security coverage + :rtype: str + """ data = {"covered_ref": covered_ref.lower().strip()} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) @@ -46,19 +66,25 @@ def generate_id(covered_ref): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from security coverage data. + + :param data: Dictionary containing 'covered_ref' key + :type data: dict + :return: STIX ID for the security coverage + :rtype: str + """ return SecurityCoverage.generate_id(data["covered_ref"]) - """ - List securityCoverage objects + def list(self, **kwargs): + """List SecurityCoverage objects. :param filters: the filters to apply :param search: the search keyword :param first: return the first n rows from the after ID (or the beginning if not set) :param after: ID of the first row for pagination - :return List of SecurityCoverage objects - """ - - def list(self, **kwargs): + :return: List of SecurityCoverage objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -136,15 +162,18 @@ def list(self, **kwargs): result["data"]["securityCoverages"], with_pagination ) - """ - Read a SecurityCoverage object + def read(self, **kwargs): + """Read a SecurityCoverage object. :param id: the id of the SecurityCoverage + :type id: str :param filters: the filters to apply if no id provided - :return SecurityCoverage object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: SecurityCoverage object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -181,13 +210,52 @@ def read(self, **kwargs): ) return None - """ - Create a Security coverage object - - :return Security Coverage object - """ - def create(self, **kwargs): + """Create a Security coverage object. + + :param name: the name of the Security Coverage (required) + :type name: str + :param objectCovered: the covered object ID (required) + :type objectCovered: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param description: (optional) description + :type description: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param external_uri: (optional) external URI + :type external_uri: str + :param coverage_last_result: (optional) last result date + :type coverage_last_result: str + :param coverage_valid_from: (optional) valid from date + :type coverage_valid_from: str + :param coverage_valid_to: (optional) valid to date + :type coverage_valid_to: str + :param coverage_information: (optional) coverage information + :type coverage_information: list + :param auto_enrichment_disable: (optional) disable auto enrichment + :type auto_enrichment_disable: bool + :param periodicity: (optional) periodicity + :type periodicity: str + :param duration: (optional) duration + :type duration: str + :param type_affinity: (optional) type affinity + :type type_affinity: str + :param platforms_affinity: (optional) platforms affinity + :type platforms_affinity: list + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Security Coverage object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) description = kwargs.get("description", None) @@ -206,6 +274,9 @@ def create(self, **kwargs): duration = kwargs.get("duration", None) type_affinity = kwargs.get("type_affinity", None) platforms_affinity = kwargs.get("platforms_affinity", None) + x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) if name is not None and object_covered is not None: self.opencti.app_logger.info("Creating Security Coverage", {"name": name}) @@ -241,6 +312,9 @@ def create(self, **kwargs): "duration": duration, "type_affinity": type_affinity, "platforms_affinity": platforms_affinity, + "x_opencti_stix_ids": x_opencti_stix_ids, + "files": files, + "filesMarkings": files_markings, } }, ) @@ -252,15 +326,20 @@ def create(self, **kwargs): "[opencti_security_coverage] " "Missing parameters: name or object_covered" ) - - """ - Import a Security coverage from a STIX2 object - - :param stixObject: the Stix-Object Security coverage - :return Security coverage object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Security coverage from a STIX2 object. + + :param stixObject: the STIX2 Security coverage object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Security coverage object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) if stix_object is not None: @@ -353,8 +432,11 @@ def import_from_stix2(self, **kwargs): if "x_opencti_stix_ids" in stix_object else None ), + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), ) else: self.opencti.app_logger.error( "[opencti_security_coverage] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_settings.py b/client-python/pycti/entities/opencti_settings.py index 0882d92fd54d..06e98f3fea5c 100644 --- a/client-python/pycti/entities/opencti_settings.py +++ b/client-python/pycti/entities/opencti_settings.py @@ -10,9 +10,17 @@ class Settings: See the properties attribute to understand which properties are fetched by default on graphql queries. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Settings instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -322,8 +330,10 @@ def delete_message(self, **kwargs) -> Optional[Dict]: """ id = kwargs.get("id", None) input = kwargs.get("input", None) - if id is None: - self.opencti.admin_logger.info("[opencti_settings] Missing parameters: id") + if id is None or input is None: + self.opencti.admin_logger.error( + "[opencti_settings] Missing parameters: id and input" + ) return None query = ( @@ -346,6 +356,13 @@ def delete_message(self, **kwargs) -> Optional[Dict]: ) def process_multiple_fields(self, data): + """Process and normalize fields in settings data. + + :param data: the settings data dictionary to process + :type data: dict + :return: the processed settings data with normalized fields + :rtype: dict + """ if "platform_messages" in data: data["platform_messages"] = self.opencti.process_multiple( data["platform_messages"] diff --git a/client-python/pycti/entities/opencti_stix.py b/client-python/pycti/entities/opencti_stix.py index 10ca0f7a71cc..43c035555e33 100644 --- a/client-python/pycti/entities/opencti_stix.py +++ b/client-python/pycti/entities/opencti_stix.py @@ -4,19 +4,26 @@ class Stix: Provides generic STIX object operations in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Stix instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - """ - Delete a Stix element + def delete(self, **kwargs): + """Delete a Stix element. :param id: the Stix element id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :param force_delete: force deletion (default: True) + :type force_delete: bool + :return: None + """ id = kwargs.get("id", None) force_delete = kwargs.get("force_delete", True) if id is not None: @@ -33,16 +40,16 @@ def delete(self, **kwargs): self.opencti.app_logger.error("[opencti_stix] Missing parameters: id") return None - """ - Merge a Stix-Object object field - - :param id: the Stix-Object id - :param key: the key of the field - :param value: the value of the field - :return The updated Stix-Object object - """ - def merge(self, **kwargs): + """Merge STIX objects into one. + + :param id: the target Stix-Object id + :type id: str + :param object_ids: list of source STIX object IDs to merge into target + :type object_ids: list + :return: The merged Stix-Object object + :rtype: dict or None + """ id = kwargs.get("id") stix_objects_ids = kwargs.get("object_ids") if id is not None and stix_objects_ids is not None: diff --git a/client-python/pycti/entities/opencti_stix_core_object.py b/client-python/pycti/entities/opencti_stix_core_object.py index 6ace4d21ed57..c14d4b0dd0f7 100644 --- a/client-python/pycti/entities/opencti_stix_core_object.py +++ b/client-python/pycti/entities/opencti_stix_core_object.py @@ -8,12 +8,16 @@ class StixCoreObject: Base class for managing STIX core objects in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the StixCoreObject instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = """ id standard_id @@ -435,18 +439,6 @@ def __init__(self, opencti, file): aliases narrative_types } - ... on DataComponent { - name - description - } - ... on DataSource { - name - description - } - ... on DataSource { - name - description - } ... on Case { name description @@ -1162,18 +1154,6 @@ def __init__(self, opencti, file): aliases narrative_types } - ... on DataComponent { - name - description - } - ... on DataSource { - name - description - } - ... on DataSource { - name - description - } ... on Case { name description @@ -1418,6 +1398,12 @@ def __init__(self, opencti, file): ... on PhoneNumber { value } + ... on TrackingNumber { + value + } + ... on Credential { + value + } ... on PaymentCard { card_number expiration_date @@ -1437,18 +1423,34 @@ def __init__(self, opencti, file): } """ - """ - List Stix-Core-Object objects + def list(self, **kwargs): + """List Stix-Core-Object objects. :param types: the list of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Stix-Core-Object objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Stix-Core-Object objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -1509,7 +1511,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["stixCoreObjects"]["pageInfo"]["hasNextPage"]: after = result["data"]["stixCoreObjects"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Stix-Core-Objects", {"after": after} ) result = self.opencti.query( @@ -1532,16 +1534,22 @@ def list(self, **kwargs): result["data"]["stixCoreObjects"], with_pagination ) - """ - Read a Stix-Core-Object object + def read(self, **kwargs): + """Read a Stix-Core-Object object. - :param id: the id of the Stix-Core-Object - :param types: list of Stix Core Entity types - :param filters: the filters to apply if no id provided - :return Stix-Core-Object object + :param id: the id of the Stix-Core-Object + :type id: str + :param types: list of Stix Core Entity types + :type types: list + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Stix-Core-Object object + :rtype: dict or None """ - - def read(self, **kwargs): id = kwargs.get("id", None) types = kwargs.get("types", None) filters = kwargs.get("filters", None) @@ -1583,6 +1591,13 @@ def read(self, **kwargs): return None def list_files(self, **kwargs): + """List files of a Stix-Core-Object. + + :param id: the id of the Stix-Core-Object + :type id: str + :return: List of files associated with the object + :rtype: list + """ id = kwargs.get("id", None) self.opencti.app_logger.debug("Listing files of Stix-Core-Object", {"id": id}) query = """ @@ -1618,6 +1633,23 @@ def push_list_export( list_filters="", mime_type=None, ): + """Push a list export file for Stix-Core-Objects. + + :param entity_id: the id of the entity (optional) + :type entity_id: str or None + :param entity_type: the type of the entity + :type entity_type: str + :param file_name: the name of the file to export + :type file_name: str + :param file_markings: list of marking definition ids to apply + :type file_markings: list + :param data: the data content to export + :type data: str + :param list_filters: filters to apply on the list (default: "") + :type list_filters: str + :param mime_type: the MIME type of the file (optional) + :type mime_type: str or None + """ query = """ mutation StixCoreObjectsExportPush($entity_id: String, $entity_type: String!, $file: Upload!, $file_markings: [String]!, $listFilters: String) { stixCoreObjectsExportPush(entity_id: $entity_id, entity_type: $entity_type, file: $file, file_markings: $file_markings, listFilters: $listFilters) @@ -1625,9 +1657,9 @@ def push_list_export( """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, { @@ -1648,6 +1680,21 @@ def push_analysis( content_type, analysis_type, ): + """Push an analysis file for a Stix-Core-Object. + + :param entity_id: the id of the Stix-Core-Object + :type entity_id: str + :param file_name: the name of the analysis file + :type file_name: str + :param data: the analysis data content + :type data: str + :param content_source: the source of the content + :type content_source: str + :param content_type: the type of analysis content + :type content_type: str + :param analysis_type: the type of analysis + :type analysis_type: str + """ query = """ mutation StixCoreObjectEdit( $id: ID!, $file: Upload!, $contentSource: String!, $contentType: AnalysisContentType!, $analysisType: String! @@ -1661,7 +1708,7 @@ def push_analysis( } """ - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) self.opencti.query( query, { @@ -1673,14 +1720,14 @@ def push_analysis( }, ) - """ - Get the reports about a Stix-Core-Object object + def reports(self, **kwargs): + """Get the reports about a Stix-Core-Object object. :param id: the id of the Stix-Core-Object - :return List of reports - """ - - def reports(self, **kwargs): + :type id: str + :return: List of reports + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -1800,15 +1847,14 @@ def reports(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Apply rule to Stix-Core-Object object + def rule_apply(self, **kwargs): + """Apply rule to Stix-Core-Object object. :param element_id: the Stix-Core-Object id + :type element_id: str :param rule_id: the rule to apply - :return void - """ - - def rule_apply(self, **kwargs): + :type rule_id: str + """ rule_id = kwargs.get("rule_id", None) element_id = kwargs.get("element_id", None) if element_id is not None and rule_id is not None: @@ -1823,19 +1869,18 @@ def rule_apply(self, **kwargs): self.opencti.query(query, {"elementId": element_id, "ruleId": rule_id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant apply rule, missing parameters: id" + "[stix_core_object] Cannot apply rule, missing parameters: id" ) return None - """ - Apply rule clear to Stix-Core-Object object + def rule_clear(self, **kwargs): + """Apply rule clear to Stix-Core-Object object. :param element_id: the Stix-Core-Object id - :param rule_id: the rule to apply - :return void - """ - - def rule_clear(self, **kwargs): + :type element_id: str + :param rule_id: the rule to clear + :type rule_id: str + """ rule_id = kwargs.get("rule_id", None) element_id = kwargs.get("element_id", None) if element_id is not None and rule_id is not None: @@ -1850,18 +1895,16 @@ def rule_clear(self, **kwargs): self.opencti.query(query, {"elementId": element_id, "ruleId": rule_id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant clear rule, missing parameters: id" + "[stix_core_object] Cannot clear rule, missing parameters: id" ) return None - """ - Apply rules rescan to Stix-Core-Object object + def rules_rescan(self, **kwargs): + """Apply rules rescan to Stix-Core-Object object. :param element_id: the Stix-Core-Object id - :return void - """ - - def rules_rescan(self, **kwargs): + :type element_id: str + """ element_id = kwargs.get("element_id", None) if element_id is not None: self.opencti.app_logger.info( @@ -1875,18 +1918,16 @@ def rules_rescan(self, **kwargs): self.opencti.query(query, {"elementId": element_id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant rescan rule, missing parameters: id" + "[stix_core_object] Cannot rescan rule, missing parameters: id" ) return None - """ - Ask clear restriction + def clear_access_restriction(self, **kwargs): + """Ask clear restriction on a Stix-Core-Object. :param element_id: the Stix-Core-Object id - :return void - """ - - def clear_access_restriction(self, **kwargs): + :type element_id: str + """ element_id = kwargs.get("element_id", None) if element_id is not None: query = """ @@ -1906,19 +1947,18 @@ def clear_access_restriction(self, **kwargs): ) else: self.opencti.app_logger.error( - "[stix_core_object] Cant clear access restriction, missing parameters: id" + "[stix_core_object] Cannot clear access restriction, missing parameters: id" ) return None - """ - Ask enrichment with single connector + def ask_enrichment(self, **kwargs): + """Ask enrichment with a single connector. :param element_id: the Stix-Core-Object id - :param connector_id the connector - :return void - """ - - def ask_enrichment(self, **kwargs): + :type element_id: str + :param connector_id: the connector id + :type connector_id: str + """ element_id = kwargs.get("element_id", None) connector_id = kwargs.get("connector_id", None) query = """ @@ -1938,15 +1978,14 @@ def ask_enrichment(self, **kwargs): }, ) - """ - Ask enrichment with multiple connectors + def ask_enrichments(self, **kwargs): + """Ask enrichment with multiple connectors. :param element_id: the Stix-Core-Object id - :param connector_ids the connectors - :return void - """ - - def ask_enrichments(self, **kwargs): + :type element_id: str + :param connector_ids: list of connector ids + :type connector_ids: list + """ element_id = kwargs.get("element_id", None) connector_ids = kwargs.get("connector_ids", None) query = """ @@ -1966,15 +2005,16 @@ def ask_enrichments(self, **kwargs): }, ) - """ - Share element to multiple organizations + def organization_share(self, entity_id, organization_ids, sharing_direct_container): + """Share element to multiple organizations. :param entity_id: the Stix-Core-Object id - :param organization_id:s the organization to share with - :return void - """ - - def organization_share(self, entity_id, organization_ids, sharing_direct_container): + :type entity_id: str + :param organization_ids: list of organization ids to share with + :type organization_ids: list + :param sharing_direct_container: whether to share direct containers + :type sharing_direct_container: bool + """ query = """ mutation StixCoreObjectEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreObjectEdit(id: $id) { @@ -1993,17 +2033,18 @@ def organization_share(self, entity_id, organization_ids, sharing_direct_contain }, ) - """ - Unshare element from multiple organizations - - :param entity_id: the Stix-Core-Object id - :param organization_id:s the organization to share with - :return void - """ - def organization_unshare( self, entity_id, organization_ids, sharing_direct_container ): + """Unshare element from multiple organizations. + + :param entity_id: the Stix-Core-Object id + :type entity_id: str + :param organization_ids: list of organization ids to unshare from + :type organization_ids: list + :param sharing_direct_container: whether to unshare direct containers + :type sharing_direct_container: bool + """ query = """ mutation StixCoreObjectEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreObjectEdit(id: $id) { @@ -2022,14 +2063,12 @@ def organization_unshare( }, ) - """ - Delete a Stix-Core-Object object + def delete(self, **kwargs): + """Delete a Stix-Core-Object object. :param id: the Stix-Core-Object id - :return void - """ - - def delete(self, **kwargs): + :type id: str + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting stix_core_object", {"id": id}) @@ -2042,17 +2081,17 @@ def delete(self, **kwargs): """ self.opencti.query(query, {"id": id}) else: - self.opencti.app_logger.error("[stix_core_object] Missing parameters: id") + self.opencti.app_logger.error( + "[opencti_stix_core_object] Missing parameters: id" + ) return None - """ - Remove a Stix-Core-Object object from draft (revert) + def remove_from_draft(self, **kwargs): + """Remove a Stix-Core-Object object from draft (revert). :param id: the Stix-Core-Object id - :return void - """ - - def remove_from_draft(self, **kwargs): + :type id: str + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Draft remove stix_core_object", {"id": id}) @@ -2066,6 +2105,6 @@ def remove_from_draft(self, **kwargs): self.opencti.query(query, {"id": id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant remove from draft, missing parameters: id" + "[stix_core_object] Cannot remove from draft, missing parameters: id" ) return None diff --git a/client-python/pycti/entities/opencti_stix_core_relationship.py b/client-python/pycti/entities/opencti_stix_core_relationship.py index 11599c8bfd52..c93d170e2e0e 100644 --- a/client-python/pycti/entities/opencti_stix_core_relationship.py +++ b/client-python/pycti/entities/opencti_stix_core_relationship.py @@ -12,9 +12,15 @@ class StixCoreRelationship: Manages STIX relationships between entities in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixCoreRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -347,6 +353,21 @@ def __init__(self, opencti): def generate_id( relationship_type, source_ref, target_ref, start_time=None, stop_time=None ): + """Generate a STIX ID for a relationship. + + :param relationship_type: The type of relationship + :type relationship_type: str + :param source_ref: The source entity reference ID + :type source_ref: str + :param target_ref: The target entity reference ID + :type target_ref: str + :param start_time: (optional) The start time of the relationship + :type start_time: str or datetime.datetime or None + :param stop_time: (optional) The stop time of the relationship + :type stop_time: str or datetime.datetime or None + :return: STIX ID for the relationship + :rtype: str + """ if isinstance(start_time, datetime.datetime): start_time = start_time.isoformat() if isinstance(stop_time, datetime.datetime): @@ -377,23 +398,15 @@ def generate_id( id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) return "relationship--" + id - """ - List stix_core_relationship objects - - :param fromId: the id of the source entity of the relation - :param toId: the id of the target entity of the relation - :param relationship_type: the relation type - :param startTimeStart: the start_time date start filter - :param startTimeStop: the start_time date stop filter - :param stopTimeStart: the stop_time date start filter - :param stopTimeStop: the stop_time date stop filter - :param first: return the first n rows from the after ID (or the beginning if not set) - :param after: ID of the first row for pagination - :return List of stix_core_relationship objects - """ - @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from relationship data. + + :param data: Dictionary containing relationship_type, source_ref, target_ref, and optionally start_time/stop_time + :type data: dict + :return: STIX ID for the relationship + :rtype: str + """ return StixCoreRelationship.generate_id( data["relationship_type"], data["source_ref"], @@ -403,6 +416,51 @@ def generate_id_from_data(data): ) def list(self, **kwargs): + """List stix_core_relationship objects. + + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str + :param elementWithTargetTypes: filter by target types + :type elementWithTargetTypes: list + :param fromId: the id of the source entity of the relation + :type fromId: str + :param fromTypes: filter by source entity types + :type fromTypes: list + :param toId: the id of the target entity of the relation + :type toId: str + :param toTypes: filter by target entity types + :type toTypes: list + :param relationship_type: the relation type + :type relationship_type: str + :param startTimeStart: the start_time date start filter + :type startTimeStart: str + :param startTimeStop: the start_time date stop filter + :type startTimeStop: str + :param stopTimeStart: the stop_time date start filter + :type stopTimeStart: str + :param stopTimeStop: the stop_time date stop filter + :type stopTimeStop: str + :param filters: additional filters to apply + :type filters: dict + :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param search: search keyword + :type search: str + :return: List of stix_core_relationship objects + :rtype: list + """ from_or_to_id = kwargs.get("fromOrToId", None) element_with_target_types = kwargs.get("elementWithTargetTypes", None) from_id = kwargs.get("fromId", None) @@ -489,7 +547,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["stixCoreRelationships"]["pageInfo"]["hasNextPage"]: after = result["data"]["stixCoreRelationships"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing StixCoreRelationships", {"after": after} ) result = self.opencti.query( @@ -511,6 +569,7 @@ def list(self, **kwargs): "after": after, "orderBy": order_by, "orderMode": order_mode, + "search": search, }, ) data = self.opencti.process_multiple( @@ -523,22 +582,34 @@ def list(self, **kwargs): result["data"]["stixCoreRelationships"], with_pagination ) - """ - Read a stix_core_relationship object + def read(self, **kwargs): + """Read a stix_core_relationship object. :param id: the id of the stix_core_relationship - :param fromOrToId: the id of the entity of the relation + :type id: str + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str :param toId: the id of the target entity of the relation + :type toId: str :param relationship_type: the relation type + :type relationship_type: str :param startTimeStart: the start_time date start filter + :type startTimeStart: str :param startTimeStop: the start_time date stop filter + :type startTimeStop: str :param stopTimeStart: the stop_time date start filter + :type stopTimeStart: str :param stopTimeStop: the stop_time date stop filter - :return stix_core_relationship object - """ - - def read(self, **kwargs): + :type stopTimeStop: str + :param filters: filters to apply + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: stix_core_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) @@ -596,14 +667,58 @@ def read(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id or from_id and to_id") return None - """ - Create a stix_core_relationship object - - :param name: the name of the Attack Pattern - :return stix_core_relationship object - """ - def create(self, **kwargs): + """Create a stix_core_relationship object. + + :param fromId: the id of the source entity + :type fromId: str + :param toId: the id of the target entity + :type toId: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param relationship_type: the type of relationship + :type relationship_type: str + :param description: (optional) description + :type description: str + :param start_time: (optional) start time of the relationship + :type start_time: str + :param stop_time: (optional) stop time of the relationship + :type stop_time: str + :param revoked: (optional) whether the relationship is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param coverage_information: (optional) coverage information + :type coverage_information: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: stix_core_relationship object + :rtype: dict or None + """ from_id = kwargs.get("fromId", None) to_id = kwargs.get("toId", None) stix_id = kwargs.get("stix_id", None) @@ -627,6 +742,7 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) coverage_information = kwargs.get("coverage_information", None) update = kwargs.get("update", False) + upsert_operations = kwargs.get("upsert_operations", None) self.opencti.app_logger.info( "Creating stix_core_relationship", @@ -673,6 +789,7 @@ def create(self, **kwargs): "x_opencti_modified_at": x_opencti_modified_at, "coverage_information": coverage_information, "update": update, + "upsertOperations": upsert_operations, } }, ) @@ -680,15 +797,16 @@ def create(self, **kwargs): result["data"]["stixCoreRelationshipAdd"] ) - """ - Update a stix_core_relationship object field + def update_field(self, **kwargs): + """Update a stix_core_relationship object field. :param id: the stix_core_relationship id + :type id: str :param input: the input of the field - :return The updated stix_core_relationship object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated stix_core_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -716,18 +834,17 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_core_relationship] Missing parameters: id and key and value", + "[opencti_stix_core_relationship] Missing parameters: id and input", ) return None - """ - Delete a stix_core_relationship + def delete(self, **kwargs): + """Delete a stix_core_relationship. :param id: the stix_core_relationship id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting stix_core_relationship", {"id": id}) @@ -745,15 +862,16 @@ def delete(self, **kwargs): ) return None - """ - Add a Marking-Definition object to stix_core_relationship object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to stix_core_relationship object (object_marking_refs). :param id: the id of the stix_core_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -812,15 +930,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to stix_core_relationship + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -847,18 +966,23 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Add a Label object to stix_core_relationship(labelging) + def add_label(self, **kwargs): + """Add a Label object to stix_core_relationship (labeling). :param id: the id of the stix_core_relationship + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def add_label(self, **kwargs): + :type label_id: str + :param label_name: (optional) the name of the Label (will create if not exists) + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -904,15 +1028,18 @@ def add_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Remove a Label object to stix_core_relationship + def remove_label(self, **kwargs): + """Remove a Label object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def remove_label(self, **kwargs): + :type label_id: str + :param label_name: (optional) the name of the Label + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -953,15 +1080,16 @@ def remove_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Add a External-Reference object to stix_core_relationship (external-reference) + def add_external_reference(self, **kwargs): + """Add an External-Reference object to stix_core_relationship. :param id: the id of the stix_core_relationship - :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -995,15 +1123,16 @@ def add_external_reference(self, **kwargs): ) return False - """ - Remove a External-Reference object to stix_core_relationship object + def remove_external_reference(self, **kwargs): + """Remove an External-Reference object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param external_reference_id: the id of the External-Reference - :return Boolean - """ - - def remove_external_reference(self, **kwargs): + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1030,18 +1159,21 @@ def remove_external_reference(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and external_reference_id" + ) return False - """ - Add a Kill-Chain-Phase object to stix_core_relationship object (kill_chain_phases) + def add_kill_chain_phase(self, **kwargs): + """Add a Kill-Chain-Phase object to stix_core_relationship object (kill_chain_phases). :param id: the id of the stix_core_relationship + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def add_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1075,15 +1207,16 @@ def add_kill_chain_phase(self, **kwargs): ) return False - """ - Remove a Kill-Chain-Phase object to stix_core_relationship object + def remove_kill_chain_phase(self, **kwargs): + """Remove a Kill-Chain-Phase object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def remove_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1110,20 +1243,21 @@ def remove_kill_chain_phase(self, **kwargs): ) return True else: - self.opencti.app_loggererror( + self.opencti.app_logger.error( "[stix_core_relationship] Missing parameters: id and kill_chain_phase_id" ) return False - """ - Update the Identity author of a stix_core_relationship object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a stix_core_relationship (created_by). :param id: the id of the stix_core_relationship + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -1197,14 +1331,20 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Import an Indicator object from a STIX2 object - - :param stixObject: the Stix-Object Indicator - :return Indicator object - """ - def import_from_stix2(self, **kwargs): + """Import a stix_core_relationship from a STIX2 object. + + :param stixRelation: the STIX2 relationship object + :type stixRelation: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :param defaultDate: default date to use for start/stop times + :type defaultDate: str or bool + :return: stix_core_relationship object + :rtype: dict or None + """ stix_relation = kwargs.get("stixRelation", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -1233,6 +1373,12 @@ def import_from_stix2(self, **kwargs): "modified_at", stix_relation ) ) + if "opencti_upsert_operations" not in stix_relation: + stix_relation["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_relation + ) + ) raw_coverages = ( stix_relation["coverage"] if "coverage" in stix_relation else [] @@ -1327,21 +1473,29 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + upsert_operations=( + stix_relation["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_relation + else None + ), ) else: self.opencti.app_logger.error( "[opencti_stix_core_relationship] Missing parameters: stixObject" ) + return None - """ - Share element to multiple organizations + def organization_share(self, entity_id, organization_ids, sharing_direct_container): + """Share element to multiple organizations. :param entity_id: the stix_core_relationship id - :param organization_id:s the organization to share with - :return void - """ - - def organization_share(self, entity_id, organization_ids, sharing_direct_container): + :type entity_id: str + :param organization_ids: the organization IDs to share with + :type organization_ids: list + :param sharing_direct_container: whether to share direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixCoreRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreRelationshipEdit(id: $id) { @@ -1360,17 +1514,19 @@ def organization_share(self, entity_id, organization_ids, sharing_direct_contain }, ) - """ - Unshare element from multiple organizations - - :param entity_id: the stix_core_relationship id - :param organization_id:s the organization to share with - :return void - """ - def organization_unshare( self, entity_id, organization_ids, sharing_direct_container ): + """Unshare element from multiple organizations. + + :param entity_id: the stix_core_relationship id + :type entity_id: str + :param organization_ids: the organization IDs to unshare from + :type organization_ids: list + :param sharing_direct_container: whether to unshare direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixCoreRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreRelationshipEdit(id: $id) { @@ -1389,14 +1545,13 @@ def organization_unshare( }, ) - """ - Remove a stix_core_relationship object from draft (revert) + def remove_from_draft(self, **kwargs): + """Remove a stix_core_relationship object from draft (revert). :param id: the stix_core_relationship id - :return void - """ - - def remove_from_draft(self, **kwargs): + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -1412,6 +1567,6 @@ def remove_from_draft(self, **kwargs): self.opencti.query(query, {"id": id}) else: self.opencti.app_logger.error( - "[stix_core_relationship] Cant remove from draft, missing parameters: id" + "[stix_core_relationship] Cannot remove from draft, missing parameters: id" ) return None diff --git a/client-python/pycti/entities/opencti_stix_cyber_observable.py b/client-python/pycti/entities/opencti_stix_cyber_observable.py index 0d7a18623dd6..3f51f0ea2f1e 100644 --- a/client-python/pycti/entities/opencti_stix_cyber_observable.py +++ b/client-python/pycti/entities/opencti_stix_cyber_observable.py @@ -23,28 +23,47 @@ class StixCyberObservable(StixCyberObservableDeprecatedMixin): Note: Deprecated methods are available through StixCyberObservableDeprecatedMixin. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the StixCyberObservable instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = SCO_PROPERTIES self.properties_with_files = SCO_PROPERTIES_WITH_FILES - """ - List StixCyberObservable objects + def list(self, **kwargs): + """List StixCyberObservable objects. :param types: the array of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) - :param after: ID of the first row - :return List of StixCyberObservable objects - """ - - def list(self, **kwargs): + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of StixCyberObservable objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -131,15 +150,20 @@ def list(self, **kwargs): result["data"]["stixCyberObservables"], with_pagination ) - """ - Read a StixCyberObservable object + def read(self, **kwargs): + """Read a StixCyberObservable object. :param id: the id of the StixCyberObservable + :type id: str :param filters: the filters to apply if no id provided - :return StixCyberObservable object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: StixCyberObservable object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -177,16 +201,28 @@ def read(self, **kwargs): ) return None - """ - Upload a file in this Observable + def add_file(self, **kwargs): + """Upload a file in this Observable. :param id: the Stix-Cyber-Observable id - :param file_name - :param data - :return void - """ - - def add_file(self, **kwargs): + :type id: str + :param file_name: name of the file to upload + :type file_name: str + :param data: the file data + :type data: bytes or str + :param fileMarkings: list of marking definition IDs for the file + :type fileMarkings: list + :param version: file version + :type version: str + :param mime_type: MIME type of the file (default: text/plain) + :type mime_type: str + :param no_trigger_import: whether to skip import trigger + :type no_trigger_import: bool + :param embedded: whether the file is embedded + :type embedded: bool + :return: updated StixCyberObservable object + :rtype: dict or None + """ id = kwargs.get("id", None) file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) @@ -221,7 +257,7 @@ def add_file(self, **kwargs): query, { "id": id, - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "fileMarkings": file_markings, "version": version, "noTriggerImport": ( @@ -234,18 +270,46 @@ def add_file(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_cyber_observable Missing parameters: id or file_name" + "[opencti_stix_cyber_observable] Missing parameters: id or file_name" ) return None - """ - Create a Stix-Observable object + def create(self, **kwargs): + """Create a Stix-Cyber-Observable object. :param observableData: the data of the observable (STIX2 structure) - :return Stix-Observable object - """ - - def create(self, **kwargs): + :type observableData: dict + :param simple_observable_id: (optional) simple observable STIX ID + :type simple_observable_id: str + :param simple_observable_key: (optional) simple observable key (e.g., "IPv4-Addr.value") + :type simple_observable_key: str + :param simple_observable_value: (optional) simple observable value + :type simple_observable_value: str + :param simple_observable_description: (optional) simple observable description + :type simple_observable_description: str + :param x_opencti_score: (optional) score (0-100) + :type x_opencti_score: int + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param resolve_result_indicators: (optional) resolve result indicators (default: True) + :type resolve_result_indicators: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Stix-Cyber-Observable object + :rtype: dict or None + """ observable_data = kwargs.get("observableData", {}) simple_observable_id = kwargs.get("simple_observable_id", None) simple_observable_key = kwargs.get("simple_observable_key", None) @@ -261,6 +325,9 @@ def create(self, **kwargs): granted_refs = kwargs.get("objectOrganization", None) update = kwargs.get("update", False) resolve_result_indicators = kwargs.get("resolve_result_indicators", True) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) create_indicator = ( observable_data["x_opencti_create_indicator"] @@ -377,6 +444,7 @@ def create(self, **kwargs): "objectLabel": object_label, "externalReferences": external_references, "update": update, + "upsertOperations": upsert_operations, } query = ( """ @@ -392,6 +460,7 @@ def create(self, **kwargs): $objectOrganization: [String], $externalReferences: [String], $update: Boolean, + $upsertOperations: [EditInput!], $AutonomousSystem: AutonomousSystemAddInput, $Directory: DirectoryAddInput, $DomainName: DomainNameAddInput, @@ -436,6 +505,7 @@ def create(self, **kwargs): objectMarking: $objectMarking, objectLabel: $objectLabel, update: $update, + upsertOperations: $upsertOperations, externalReferences: $externalReferences, objectOrganization: $objectOrganization, AutonomousSystem: $AutonomousSystem, @@ -505,6 +575,8 @@ def create(self, **kwargs): observable_data["name"] if "name" in observable_data else None ), "rir": observable_data["rir"] if "rir" in observable_data else None, + "files": files, + "filesMarkings": files_markings, } elif type == "Directory": input_variables["Directory"] = { @@ -523,9 +595,15 @@ def create(self, **kwargs): "atime": ( observable_data["atime"] if "atime" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Domain-Name": - input_variables["DomainName"] = {"value": observable_data["value"]} + input_variables["DomainName"] = { + "value": observable_data["value"], + "files": files, + "filesMarkings": files_markings, + } if attribute is not None: input_variables["DomainName"][attribute] = simple_observable_value elif type == "Email-Addr": @@ -536,6 +614,8 @@ def create(self, **kwargs): if "display_name" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Email-Message": input_variables["EmailMessage"] = { @@ -565,6 +645,8 @@ def create(self, **kwargs): "body": ( observable_data["body"] if "body" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Email-Mime-Part-Type": input_variables["EmailMimePartType"] = { @@ -581,6 +663,8 @@ def create(self, **kwargs): if "content_disposition" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Artifact": if ( @@ -618,6 +702,8 @@ def create(self, **kwargs): if "x_opencti_additional_names" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "StixFile": if ( @@ -669,6 +755,8 @@ def create(self, **kwargs): if "x_opencti_additional_names" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "X509-Certificate": input_variables["X509Certificate"] = { @@ -808,6 +896,8 @@ def create(self, **kwargs): if "policy_mappings" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "SSH-Key" or type.lower() == "ssh-key": input_variables["SSHKey"] = { @@ -851,30 +941,40 @@ def create(self, **kwargs): if "expiration_date" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "IPv4-Addr": input_variables["IPv4Addr"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "IPv6-Addr": input_variables["IPv6Addr"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Mac-Addr": input_variables["MacAddr"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Mutex": input_variables["Mutex"] = { "name": ( observable_data["name"] if "name" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Network-Traffic": input_variables["NetworkTraffic"] = { @@ -932,6 +1032,8 @@ def create(self, **kwargs): if "dst_packets" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Process": input_variables["Process"] = { @@ -957,6 +1059,8 @@ def create(self, **kwargs): if "environment_variables" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Software": if ( @@ -999,12 +1103,16 @@ def create(self, **kwargs): if "x_opencti_product" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Url": input_variables["Url"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "User-Account": input_variables["UserAccount"] = { @@ -1078,6 +1186,8 @@ def create(self, **kwargs): if "account_last_login" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Windows-Registry-Key": input_variables["WindowsRegistryKey"] = { @@ -1094,6 +1204,8 @@ def create(self, **kwargs): if "number_of_subkeys" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Windows-Registry-Value-Type": input_variables["WindowsRegistryValueType"] = { @@ -1108,30 +1220,40 @@ def create(self, **kwargs): if "data_type" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "User-Agent": input_variables["UserAgent"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Cryptographic-Key": input_variables["CryptographicKey"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Hostname": input_variables["Hostname"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Text": input_variables["Text"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Bank-Account": input_variables["BankAccount"] = { @@ -1144,12 +1266,16 @@ def create(self, **kwargs): if "account_number" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Phone-Number": input_variables["PhoneNumber"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Payment-Card": input_variables["PaymentCard"] = { @@ -1169,6 +1295,8 @@ def create(self, **kwargs): if "holder_name" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Media-Content": input_variables["MediaContent"] = { @@ -1191,6 +1319,8 @@ def create(self, **kwargs): if "publication_date" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Persona": input_variables["Persona"] = { @@ -1204,6 +1334,8 @@ def create(self, **kwargs): if "persona_type" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Payment-Card" or type.lower() == "x-opencti-payment-card": input_variables["PaymentCard"] = { @@ -1223,6 +1355,8 @@ def create(self, **kwargs): if "holder_name" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif ( type == "Cryptocurrency-Wallet" @@ -1232,12 +1366,16 @@ def create(self, **kwargs): "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif type == "Credential" or type.lower() == "x-opencti-credential": input_variables["Credential"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } elif ( type == "Tracking-Number" or type.lower() == "x-opencti-tracking-number" @@ -1246,6 +1384,8 @@ def create(self, **kwargs): "value": ( observable_data["value"] if "value" in observable_data else None ), + "files": files, + "filesMarkings": files_markings, } result = self.opencti.query(query, input_variables) if "payload_bin" in observable_data and "mime_type" in observable_data: @@ -1265,15 +1405,30 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error("Missing parameters: type") - - """ - Upload an artifact - - :param file_path: the file path - :return Stix-Observable object - """ + return None def upload_artifact(self, **kwargs): + """Upload an artifact. + + :param file_name: the file name or path + :type file_name: str + :param data: the file data (optional, reads from file_name if not provided) + :type data: bytes + :param mime_type: the MIME type (default: text/plain) + :type mime_type: str + :param x_opencti_description: description for the artifact + :type x_opencti_description: str + :param createdBy: the author ID + :type createdBy: str + :param objectMarking: list of marking definition IDs + :type objectMarking: list + :param objectLabel: list of label IDs + :type objectLabel: list + :param createIndicator: whether to create an indicator (default: False) + :type createIndicator: bool + :return: Stix-Observable object + :rtype: dict or None + """ file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) mime_type = kwargs.get("mime_type", "text/plain") @@ -1403,7 +1558,7 @@ def upload_artifact(self, **kwargs): result = self.opencti.query( query, { - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "x_opencti_description": x_opencti_description, "createdBy": created_by, "objectMarking": object_marking, @@ -1415,16 +1570,18 @@ def upload_artifact(self, **kwargs): ) else: self.opencti.app_logger.error("Missing parameters: type") + return None - """ - Update a Stix-Observable object field + def update_field(self, **kwargs): + """Update a Stix-Observable object field. :param id: the Stix-Observable id - :param input: the input of the field - :return The updated Stix-Observable object - """ - - def update_field(self, **kwargs): + :type id: str + :param input: the input of the field to update + :type input: list + :return: The updated Stix-Observable object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -1456,14 +1613,16 @@ def update_field(self, **kwargs): ) return None - """ - Promote a Stix-Observable to an Indicator + def promote_to_indicator_v2(self, **kwargs): + """Promote a Stix-Observable to an Indicator. :param id: the Stix-Observable id - :return the newly created indicator - """ - - def promote_to_indicator_v2(self, **kwargs): + :type id: str + :param customAttributes: custom attributes to return for the indicator + :type customAttributes: str + :return: The newly created indicator + :rtype: dict or None + """ id = kwargs.get("id", None) custom_attributes = kwargs.get("customAttributes", None) if id is not None: @@ -1495,14 +1654,14 @@ def promote_to_indicator_v2(self, **kwargs): ) return None - """ - Delete a Stix-Observable + def delete(self, **kwargs): + """Delete a Stix-Observable. :param id: the Stix-Observable id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :return: None + :rtype: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Stix-Observable", {"id": id}) @@ -1520,15 +1679,16 @@ def delete(self, **kwargs): ) return None - """ - Update the Identity author of a Stix-Cyber-Observable object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a Stix-Cyber-Observable object (created_by). :param id: the id of the Stix-Cyber-Observable + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -1602,15 +1762,16 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Add a Marking-Definition object to Stix-Cyber-Observable object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to Stix-Cyber-Observable object (object_marking_refs). :param id: the id of the Stix-Cyber-Observable + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1666,15 +1827,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to Stix-Cyber-Observable object + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1701,18 +1863,23 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Add a Label object to Stix-Cyber-Observable object + def add_label(self, **kwargs): + """Add a Label object to Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def add_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (will create if not exists) + :type label_name: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1758,15 +1925,18 @@ def add_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Remove a Label object to Stix-Cyber-Observable object + def remove_label(self, **kwargs): + """Remove a Label object from Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def remove_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (alternative to label_id) + :type label_name: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1807,15 +1977,16 @@ def remove_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Add a External-Reference object to Stix-Cyber-Observable object (object_marking_refs) + def add_external_reference(self, **kwargs): + """Add an External-Reference object to Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1877,15 +2048,16 @@ def add_external_reference(self, **kwargs): ) return False - """ - Remove a Label object to Stix-Cyber-Observable object + def remove_external_reference(self, **kwargs): + """Remove an External-Reference object from Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :param label_id: the id of the Label - :return Boolean - """ - - def remove_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1912,7 +2084,9 @@ def remove_external_reference(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and external_reference_id" + ) return False def push_list_export( @@ -1925,6 +2099,25 @@ def push_list_export( list_filters="", mime_type=None, ): + """Push a list export for Stix-Cyber-Observables. + + :param entity_id: the entity ID + :type entity_id: str + :param entity_type: the entity type + :type entity_type: str + :param file_name: the file name + :type file_name: str + :param file_markings: list of marking definition IDs for the file + :type file_markings: list + :param data: the file data + :type data: bytes + :param list_filters: the list filters (default: "") + :type list_filters: str + :param mime_type: the MIME type (optional) + :type mime_type: str + :return: None + :rtype: None + """ query = """ mutation StixCyberObservablesExportPush( $entity_id: String, @@ -1943,9 +2136,9 @@ def push_list_export( } """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, { @@ -1958,6 +2151,15 @@ def push_list_export( ) def ask_for_enrichment(self, **kwargs) -> str: + """Ask for enrichment of a Stix-Cyber-Observable. + + :param id: the id of the Stix-Cyber-Observable + :type id: str + :param connector_id: the id of the enrichment connector + :type connector_id: str + :return: The work ID + :rtype: str + """ id = kwargs.get("id", None) connector_id = kwargs.get("connector_id", None) @@ -1985,14 +2187,14 @@ def ask_for_enrichment(self, **kwargs) -> str: # return work_id return result["data"]["stixCoreObjectEdit"]["askEnrichment"]["id"] - """ - Get the reports about a Stix-Cyber-Observable object + def reports(self, **kwargs): + """Get the reports about a Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :return List of reports - """ - - def reports(self, **kwargs): + :type id: str + :return: List of reports + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2112,14 +2314,14 @@ def reports(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the notes about a Stix-Cyber-Observable object + def notes(self, **kwargs): + """Get the notes about a Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :return List of notes - """ - - def notes(self, **kwargs): + :type id: str + :return: List of notes + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2240,14 +2442,14 @@ def notes(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the observed data of a Stix-Cyber-Observable object + def observed_data(self, **kwargs): + """Get the observed data of a Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :return List of observed data - """ - - def observed_data(self, **kwargs): + :type id: str + :return: List of observed data + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( diff --git a/client-python/pycti/entities/opencti_stix_domain_object.py b/client-python/pycti/entities/opencti_stix_domain_object.py index 64b0eda41659..b9b71b9a13d8 100644 --- a/client-python/pycti/entities/opencti_stix_domain_object.py +++ b/client-python/pycti/entities/opencti_stix_domain_object.py @@ -12,12 +12,16 @@ class StixDomainObject: Manages STIX Domain Objects in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the StixDomainObject instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = """ id standard_id @@ -1095,18 +1099,34 @@ def __init__(self, opencti, file): } """ - """ - List Stix-Domain-Object objects + def list(self, **kwargs): + """List Stix-Domain-Object objects. :param types: the list of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Stix-Domain-Object objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Stix-Domain-Object objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -1167,7 +1187,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["stixDomainObjects"]["pageInfo"]["hasNextPage"]: after = result["data"]["stixDomainObjects"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Stix-Domain-Objects", {"after": after} ) result = self.opencti.query( @@ -1192,16 +1212,22 @@ def list(self, **kwargs): result["data"]["stixDomainObjects"], with_pagination ) - """ - Read a Stix-Domain-Object object + def read(self, **kwargs): + """Read a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param types: list of Stix Domain Entity types + :type types: list :param filters: the filters to apply if no id provided - :return Stix-Domain-Object object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Stix-Domain-Object object + :rtype: dict or None + """ id = kwargs.get("id", None) types = kwargs.get("types", None) filters = kwargs.get("filters", None) @@ -1242,16 +1268,24 @@ def read(self, **kwargs): ) return None - """ - Get a Stix-Domain-Object object by stix_id or name + def get_by_stix_id_or_name(self, **kwargs): + """Get a Stix-Domain-Object object by stix_id or name. :param types: a list of Stix-Domain-Object types + :type types: list :param stix_id: the STIX ID of the Stix-Domain-Object + :type stix_id: str :param name: the name of the Stix-Domain-Object - :return Stix-Domain-Object object - """ - - def get_by_stix_id_or_name(self, **kwargs): + :type name: str + :param aliases: list of aliases to search + :type aliases: list + :param fieldName: the field name to use for alias search + :type fieldName: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Stix-Domain-Object object + :rtype: dict or None + """ types = kwargs.get("types", None) stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) @@ -1295,14 +1329,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Update a Stix-Domain-Object object field + def update_field(self, **kwargs): + """Update a Stix-Domain-Object object field. :param id: the Stix-Domain-Object id + :type id: str :param input: the input of the field - """ - - def update_field(self, **kwargs): + :type input: list + :return: Updated Stix-Domain-Object object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -1334,14 +1370,13 @@ def update_field(self, **kwargs): ) return None - """ - Delete a Stix-Domain-Object + def delete(self, **kwargs): + """Delete a Stix-Domain-Object. :param id: the Stix-Domain-Object id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Stix-Domain-Object", {"id": id}) @@ -1359,16 +1394,28 @@ def delete(self, **kwargs): ) return None - """ - Upload a file in this Stix-Domain-Object + def add_file(self, **kwargs): + """Upload a file to this Stix-Domain-Object. :param id: the Stix-Domain-Object id - :param file_name - :param data - :return void - """ - - def add_file(self, **kwargs): + :type id: str + :param file_name: the file name or path + :type file_name: str + :param data: the file data (optional, will read from file_name if not provided) + :type data: bytes or None + :param fileMarkings: list of marking definition IDs for the file + :type fileMarkings: list + :param version: version datetime + :type version: str + :param mime_type: MIME type of the file + :type mime_type: str + :param no_trigger_import: whether to skip triggering import + :type no_trigger_import: bool + :param embedded: whether the file is embedded + :type embedded: bool + :return: File upload result + :rtype: dict or None + """ id = kwargs.get("id", None) file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) @@ -1403,7 +1450,7 @@ def add_file(self, **kwargs): query, { "id": id, - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "fileMarkings": file_markings, "version": version, "noTriggerImport": ( @@ -1430,15 +1477,33 @@ def push_list_export( list_filters="", mime_type=None, ): + """Push a list export file. + + :param entity_id: the entity id + :type entity_id: str + :param entity_type: the entity type + :type entity_type: str + :param file_name: the file name + :type file_name: str + :param file_markings: list of marking definition IDs + :type file_markings: list + :param data: the file data + :type data: bytes or str + :param list_filters: filters applied to the list export + :type list_filters: str + :param mime_type: MIME type of the file + :type mime_type: str or None + :return: None + """ query = """ mutation StixDomainObjectsExportPush($entity_id: String, $entity_type: String!, $file: Upload!, $file_markings: [String]!, $listFilters: String) { stixDomainObjectsExportPush(entity_id: $entity_id, entity_type: $entity_type, file: $file, file_markings: $file_markings, listFilters: $listFilters) } """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, { @@ -1453,6 +1518,20 @@ def push_list_export( def push_entity_export( self, entity_id, file_name, data, file_markings=None, mime_type=None ): + """Push an entity export file. + + :param entity_id: the entity id + :type entity_id: str + :param file_name: the file name + :type file_name: str + :param data: the file data + :type data: bytes or str + :param file_markings: list of marking definition IDs + :type file_markings: list or None + :param mime_type: MIME type of the file + :type mime_type: str or None + :return: None + """ if file_markings is None: file_markings = [] query = """ @@ -1469,22 +1548,23 @@ def push_entity_export( } """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, {"id": entity_id, "file": file, "file_markings": file_markings} ) - """ - Update the Identity author of a Stix-Domain-Object object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a Stix-Domain-Object object (created_by). :param id: the id of the Stix-Domain-Object + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -1558,15 +1638,16 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Add a Marking-Definition object to Stix-Domain-Object object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to Stix-Domain-Object object (object_marking_refs). :param id: the id of the Stix-Domain-Object + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1623,15 +1704,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to Stix-Domain-Object object + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1658,18 +1740,23 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Add a Label object to Stix-Domain-Object object + def add_label(self, **kwargs): + """Add a Label object to Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def add_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (alternative to label_id) + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1714,15 +1801,18 @@ def add_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Remove a Label object to Stix-Domain-Object object + def remove_label(self, **kwargs): + """Remove a Label object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def remove_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (alternative to label_id) + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1763,15 +1853,16 @@ def remove_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Add a External-Reference object to Stix-Domain-Object object (object_marking_refs) + def add_external_reference(self, **kwargs): + """Add an External-Reference object to Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1805,15 +1896,16 @@ def add_external_reference(self, **kwargs): ) return False - """ - Remove a Label object to Stix-Domain-Object object + def remove_external_reference(self, **kwargs): + """Remove an External-Reference object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :param label_id: the id of the Label - :return Boolean - """ - - def remove_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1840,18 +1932,21 @@ def remove_external_reference(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and external_reference_id" + ) return False - """ - Add a Kill-Chain-Phase object to Stix-Domain-Object object (kill_chain_phases) + def add_kill_chain_phase(self, **kwargs): + """Add a Kill-Chain-Phase object to Stix-Domain-Object object (kill_chain_phases). :param id: the id of the Stix-Domain-Object + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def add_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1885,15 +1980,16 @@ def add_kill_chain_phase(self, **kwargs): ) return False - """ - Remove a Kill-Chain-Phase object to Stix-Domain-Object object + def remove_kill_chain_phase(self, **kwargs): + """Remove a Kill-Chain-Phase object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def remove_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1925,14 +2021,14 @@ def remove_kill_chain_phase(self, **kwargs): ) return False - """ - Get the reports about a Stix-Domain-Object object + def reports(self, **kwargs): + """Get the reports about a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :return List of reports - """ - - def reports(self, **kwargs): + :type id: str + :return: List of reports + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2052,14 +2148,14 @@ def reports(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the notes about a Stix-Domain-Object object + def notes(self, **kwargs): + """Get the notes about a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :return List of notes - """ - - def notes(self, **kwargs): + :type id: str + :return: List of notes + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2180,14 +2276,14 @@ def notes(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the observed data of a Stix-Domain-Object object + def observed_data(self, **kwargs): + """Get the observed data of a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :return List of observed data - """ - - def observed_data(self, **kwargs): + :type id: str + :return: List of observed data + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( diff --git a/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py b/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py index 369022893c18..2cab25c13f39 100644 --- a/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py +++ b/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py @@ -4,9 +4,15 @@ class StixNestedRefRelationship: Manages nested reference relationships in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixNestedRefRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -67,22 +73,36 @@ def __init__(self, opencti): } """ - """ - List stix_observable_relationship objects + def list(self, **kwargs): + """List stix nested ref relationship objects. + :param fromOrToId: the id of either the source or target entity + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str + :param fromTypes: the types of the source entities + :type fromTypes: list :param toId: the id of the target entity of the relation + :type toId: str + :param toTypes: the types of the target entities + :type toTypes: list :param relationship_type: the relation type + :type relationship_type: str :param startTimeStart: the first_seen date start filter + :type startTimeStart: str :param startTimeStop: the first_seen date stop filter + :type startTimeStop: str :param stopTimeStart: the last_seen date start filter + :type stopTimeStart: str :param stopTimeStop: the last_seen date stop filter + :type stopTimeStop: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of stix_observable_relationship objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of stix nested ref relationship objects + :rtype: list + """ from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) from_types = kwargs.get("fromTypes", None) @@ -156,22 +176,34 @@ def list(self, **kwargs): result["data"]["stixNestedRefRelationships"], with_pagination ) - """ - Read a stix_observable_relationship object + def read(self, **kwargs): + """Read a stix nested ref relationship object. - :param id: the id of the stix_observable_relationship - :param stix_id: the STIX id of the stix_observable_relationship + :param id: the id of the stix nested ref relationship + :type id: str + :param fromOrToId: the id of either the source or target entity + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str :param toId: the id of the target entity of the relation + :type toId: str :param relationship_type: the relation type + :type relationship_type: str :param startTimeStart: the first_seen date start filter + :type startTimeStart: str :param startTimeStop: the first_seen date stop filter + :type startTimeStop: str :param stopTimeStart: the last_seen date start filter + :type stopTimeStart: str :param stopTimeStop: the last_seen date stop filter - :return stix_observable_relationship object - """ - - def read(self, **kwargs): + :type stopTimeStop: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param filters: the filters to apply + :type filters: dict + :return: stix nested ref relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) @@ -228,14 +260,36 @@ def read(self, **kwargs): else: return None - """ - Create a stix_observable_relationship object - - :param from_id: id of the source entity - :return stix_observable_relationship object - """ - def create(self, **kwargs): + """Create a stix nested ref relationship object. + + :param fromId: the id of the source entity + :type fromId: str + :param toId: the id of the target entity + :type toId: str + :param relationship_type: the type of the relationship + :type relationship_type: str + :param start_time: (optional) the start time of the relationship + :type start_time: str + :param stop_time: (optional) the stop time of the relationship + :type stop_time: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param createdBy: (optional) the creator ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists + :type update: bool + :return: stix nested ref relationship object + :rtype: dict + """ from_id = kwargs.get("fromId", None) to_id = kwargs.get("toId", None) relationship_type = kwargs.get("relationship_type", None) @@ -297,15 +351,16 @@ def create(self, **kwargs): result["data"]["stixRefRelationshipAdd"] ) - """ - Update a stix_observable_relationship object field - - :param id: the stix_observable_relationship id - :param input: the input of the field - :return The updated stix_observable_relationship object - """ - def update_field(self, **kwargs): + """Update a stix nested ref relationship object field. + + :param id: the stix nested ref relationship id + :type id: str + :param input: the input of the field to update + :type input: list + :return: The updated stix nested ref relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -330,5 +385,7 @@ def update_field(self, **kwargs): result["data"]["stixRefRelationshipEdit"]["fieldPatch"] ) else: - self.opencti.app_logger.error("Missing parameters: id and key and value") + self.opencti.app_logger.error( + "[opencti_stix_nested_ref_relationship] Missing parameters: id and input" + ) return None diff --git a/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py b/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py index 036e9ea2038b..dd1ba553258c 100644 --- a/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py +++ b/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py @@ -7,9 +7,15 @@ class StixObjectOrStixRelationship: Manages generic STIX objects and relationships in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixObjectOrStixRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ ... on StixObject { @@ -521,14 +527,18 @@ def __init__(self, opencti): } """ - """ - Read a StixObjectOrStixRelationship object + def read(self, **kwargs): + """Read a StixObjectOrStixRelationship object. :param id: the id of the StixObjectOrStixRelationship - :return StixObjectOrStixRelationship object - """ - - def read(self, **kwargs): + :type id: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param filters: the filters to apply + :type filters: dict + :return: StixObjectOrStixRelationship object + :rtype: dict or None + """ id = kwargs.get("id", None) custom_attributes = kwargs.get("customAttributes", None) filters = kwargs.get("filters", None) @@ -566,6 +576,25 @@ def read(self, **kwargs): return None def list(self, **kwargs): + """List StixObjectOrStixRelationship objects. + + :param filters: the filters to apply + :type filters: dict + :param search: the search keyword + :type search: str + :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param with_pagination: whether to include pagination info + :type with_pagination: bool + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: List of StixObjectOrStixRelationship objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -623,7 +652,7 @@ def list(self, **kwargs): after = result["data"]["stixObjectOrStixRelationships"]["pageInfo"][ "endCursor" ] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing stixObjectOrStixRelationships", {"after": after} ) after_variables = {**variables, **{"after": after}} diff --git a/client-python/pycti/entities/opencti_stix_sighting_relationship.py b/client-python/pycti/entities/opencti_stix_sighting_relationship.py index 9ff3f63c9eb1..3942b52c7b86 100644 --- a/client-python/pycti/entities/opencti_stix_sighting_relationship.py +++ b/client-python/pycti/entities/opencti_stix_sighting_relationship.py @@ -12,9 +12,15 @@ class StixSightingRelationship: Manages STIX sighting relationships in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixSightingRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -283,6 +289,19 @@ def generate_id( first_seen=None, last_seen=None, ): + """Generate a STIX ID for a Sighting relationship. + + :param sighting_of_ref: The STIX ID of the entity being sighted + :type sighting_of_ref: str + :param where_sighted_refs: The STIX IDs of where the sighting occurred + :type where_sighted_refs: list + :param first_seen: (optional) The first seen date + :type first_seen: str or datetime.datetime or None + :param last_seen: (optional) The last seen date + :type last_seen: str or datetime.datetime or None + :return: STIX ID for the sighting + :rtype: str + """ if isinstance(first_seen, datetime.datetime): first_seen = first_seen.isoformat() if isinstance(last_seen, datetime.datetime): @@ -315,6 +334,13 @@ def generate_id( @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from sighting data. + + :param data: Dictionary containing sighting_of_ref, where_sighted_refs, and optionally first_seen/last_seen + :type data: dict + :return: STIX ID for the sighting + :rtype: str + """ return StixSightingRelationship.generate_id( data["sighting_of_ref"], data["where_sighted_refs"], @@ -322,21 +348,48 @@ def generate_id_from_data(data): data.get("last_seen"), ) - """ - List stix_sightings objects + def list(self, **kwargs): + """List stix_sighting_relationship objects. + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str + :param fromTypes: filter by source entity types + :type fromTypes: list :param toId: the id of the target entity of the relation + :type toId: str + :param toTypes: filter by target entity types + :type toTypes: list :param firstSeenStart: the first_seen date start filter + :type firstSeenStart: str :param firstSeenStop: the first_seen date stop filter + :type firstSeenStop: str :param lastSeenStart: the last_seen date start filter + :type lastSeenStart: str :param lastSeenStop: the last_seen date stop filter + :type lastSeenStop: str + :param filters: additional filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of stix_sighting objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param search: search keyword + :type search: str + :return: List of stix_sighting_relationship objects + :rtype: list + """ from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) from_types = kwargs.get("fromTypes", None) @@ -414,7 +467,7 @@ def list(self, **kwargs): after = result["data"]["stixSightingRelationships"]["pageInfo"][ "endCursor" ] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing StixSightingRelationships", {"after": after} ) result = self.opencti.query( @@ -434,6 +487,7 @@ def list(self, **kwargs): "after": after, "orderBy": order_by, "orderMode": order_mode, + "search": search, }, ) data = self.opencti.process_multiple( @@ -446,20 +500,32 @@ def list(self, **kwargs): result["data"]["stixSightingRelationships"], with_pagination ) - """ - Read a stix_sighting object + def read(self, **kwargs): + """Read a stix_sighting_relationship object. - :param id: the id of the stix_sighting + :param id: the id of the stix_sighting_relationship + :type id: str + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str :param toId: the id of the target entity of the relation + :type toId: str :param firstSeenStart: the first_seen date start filter + :type firstSeenStart: str :param firstSeenStop: the first_seen date stop filter + :type firstSeenStop: str :param lastSeenStart: the last_seen date start filter + :type lastSeenStart: str :param lastSeenStop: the last_seen date stop filter - :return stix_sighting object - """ - - def read(self, **kwargs): + :type lastSeenStop: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param filters: filters to apply + :type filters: dict + :return: stix_sighting_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) @@ -515,14 +581,52 @@ def read(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id or from_id and to_id") return None - """ - Create a stix_sighting object - - :param name: the name of the Attack Pattern - :return stix_sighting object - """ - def create(self, **kwargs): + """Create a stix_sighting_relationship object. + + :param fromId: the id of the source entity + :type fromId: str + :param toId: the id of the target entity + :type toId: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param description: (optional) description + :type description: str + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param count: (optional) sighting count + :type count: int + :param x_opencti_negative: (optional) whether this is a negative sighting + :type x_opencti_negative: bool + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: stix_sighting_relationship object + :rtype: dict or None + """ from_id = kwargs.get("fromId", None) to_id = kwargs.get("toId", None) stix_id = kwargs.get("stix_id", None) @@ -543,6 +647,7 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) granted_refs = kwargs.get("objectOrganization", None) update = kwargs.get("update", False) + upsert_operations = kwargs.get("upsert_operations", None) self.opencti.app_logger.info( "Creating stix_sighting", {"from_id": from_id, "to_id": to_id} @@ -581,6 +686,7 @@ def create(self, **kwargs): "x_opencti_modified_at": x_opencti_modified_at, "objectOrganization": granted_refs, "update": update, + "upsertOperations": upsert_operations, } }, ) @@ -588,15 +694,16 @@ def create(self, **kwargs): result["data"]["stixSightingRelationshipAdd"] ) - """ - Update a stix_sighting object field + def update_field(self, **kwargs): + """Update a stix_sighting_relationship object field. - :param id: the stix_sighting id + :param id: the stix_sighting_relationship id + :type id: str :param input: the input of the field - :return The updated stix_sighting object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated stix_sighting_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -622,19 +729,20 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_sighting] Missing parameters: id and key and value" + "[opencti_stix_sighting] Missing parameters: id and input" ) return None - """ - Add a Marking-Definition object to stix_sighting_relationship object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to stix_sighting_relationship object (object_marking_refs). :param id: the id of the stix_sighting_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -693,15 +801,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to stix_sighting_relationship + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from stix_sighting_relationship. :param id: the id of the stix_sighting_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -728,18 +837,21 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Update the Identity author of a stix_sighting_relationship object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a stix_sighting_relationship object (created_by). :param id: the id of the stix_sighting_relationship + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -813,15 +925,17 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Share element to multiple organizations - - :param entity_id: the stix_sighting id - :param organization_id:s the organization to share with - :return void - """ - def organization_share(self, entity_id, organization_ids, sharing_direct_container): + """Share element to multiple organizations. + + :param entity_id: the stix_sighting_relationship id + :type entity_id: str + :param organization_ids: the organization IDs to share with + :type organization_ids: list + :param sharing_direct_container: whether to share direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixSightingRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixSightingRelationshipEdit(id: $id) { @@ -840,17 +954,19 @@ def organization_share(self, entity_id, organization_ids, sharing_direct_contain }, ) - """ - Unshare element from multiple organizations - - :param entity_id: the stix_sighting id - :param organization_id:s the organization to share with - :return void - """ - def organization_unshare( self, entity_id, organization_ids, sharing_direct_container ): + """Unshare element from multiple organizations. + + :param entity_id: the stix_sighting_relationship id + :type entity_id: str + :param organization_ids: the organization IDs to unshare from + :type organization_ids: list + :param sharing_direct_container: whether to unshare direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixSightingRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixSightingRelationshipEdit(id: $id) { @@ -869,14 +985,13 @@ def organization_unshare( }, ) - """ - Remove a stix_sighting object from draft (revert) - - :param id: the stix_sighting id - :return void - """ - def remove_from_draft(self, **kwargs): + """Remove a stix_sighting_relationship object from draft (revert). + + :param id: the stix_sighting_relationship id + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Draft remove stix_sighting", {"id": id}) @@ -890,18 +1005,17 @@ def remove_from_draft(self, **kwargs): self.opencti.query(query, {"id": id}) else: self.opencti.app_logger.error( - "[stix_sighting] Cant remove from draft, missing parameters: id" + "[stix_sighting] Cannot remove from draft, missing parameters: id" ) return None - """ - Delete a stix_sighting - - :param id: the stix_sighting id - :return void - """ - def delete(self, **kwargs): + """Delete a stix_sighting_relationship. + + :param id: the stix_sighting_relationship id + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting stix_sighting", {"id": id}) diff --git a/client-python/pycti/entities/opencti_task.py b/client-python/pycti/entities/opencti_task.py index 4dbd5b35a094..ec3c61d793ad 100644 --- a/client-python/pycti/entities/opencti_task.py +++ b/client-python/pycti/entities/opencti_task.py @@ -12,9 +12,15 @@ class Task: Manages tasks and to-do items in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Task instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -241,6 +247,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Task object. + + :param name: the name of the Task + :type name: str + :param created: the creation date of the Task + :type created: str or datetime.datetime + :return: STIX ID for the Task + :rtype: str + """ if isinstance(created, datetime.datetime): created = created.isoformat() data = {"name": name.lower().strip(), "created": created} @@ -250,19 +265,29 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Task data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Task + :rtype: str + """ return Task.generate_id(data["name"], data["created"]) - """ - List Task objects - + def list(self, **kwargs): + """List Task objects. + :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Task objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Task objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -335,15 +360,16 @@ def list(self, **kwargs): result["data"]["tasks"], with_pagination ) - """ - Read a Task object + def read(self, **kwargs): + """Read a Task object. :param id: the id of the Task + :type id: str :param filters: the filters to apply if no id provided - :return Task object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Task object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -372,17 +398,24 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Read a Task object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ + else: + self.opencti.app_logger.error( + "[opencti_task] Missing parameters: id or filters" + ) + return None def get_by_stix_id_or_name(self, **kwargs): + """Read a Task object by stix_id or name. + + :param stix_id: the STIX ID of the Task + :type stix_id: str + :param name: the name of the Task + :type name: str + :param created: the creation date of the Task + :type created: str + :return: Task object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -405,15 +438,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a task already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a task already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Task + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if the task contains the entity, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -441,17 +475,30 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["taskContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_Task] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_task] Missing parameters: id or stixObjectOrStixRelationshipId" ) + return None - """ - Create a Task object + def create(self, **kwargs): + """Create a Task object. :param name: the name of the Task - :return Task object - """ - - def create(self, **kwargs): + :type name: str + :param description: the description of the Task + :type description: str + :param due_date: the due date of the Task + :type due_date: str + :param createdBy: the creator of the Task + :type createdBy: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Task object + :rtype: dict or None + """ objects = kwargs.get("objects", None) created = kwargs.get("created", None) name = kwargs.get("name", None) @@ -466,6 +513,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Task", {"name": name}) @@ -497,14 +547,27 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) return self.opencti.process_multiple_fields(result["data"]["taskAdd"]) else: self.opencti.app_logger.error("[opencti_task] Missing parameters: name") + return None def update_field(self, **kwargs): + """Update a field of a Task object. + + :param id: the id of the Task + :type id: str + :param input: the input containing field(s) to update + :type input: list + :return: Task object + :rtype: dict or None + """ self.opencti.app_logger.info("Updating Task", {"data": json.dumps(kwargs)}) id = kwargs.get("id", None) input = kwargs.get("input", None) @@ -524,19 +587,20 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_Task] Missing parameters: id and key and value" + "[opencti_task] Missing parameters: id and input" ) return None - """ - Add a Stix-Entity object to Task object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Task object (object_refs). :param id: the id of the Task + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, None if parameters are missing + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -573,15 +637,16 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Task object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Task object (object_refs). :param id: the id of the Task + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -596,7 +661,7 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) query = """ mutation taskEditRelationDelete($id: ID!, $toId: StixRef!, $relationship_type: String!) { - taskRelationAdd(id: $id, toId: $toId, relationship_type: $relationship_type) { + taskRelationDelete(id: $id, toId: $toId, relationship_type: $relationship_type) { id } } @@ -616,14 +681,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Task object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Task object from a STIX2 object. :param stixObject: the Stix-Object Task - :return Task object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Task object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -655,6 +724,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -703,13 +778,27 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_task] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Task object. + + :param id: the id of the Task to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Task", {"id": id}) diff --git a/client-python/pycti/entities/opencti_threat_actor.py b/client-python/pycti/entities/opencti_threat_actor.py index 47cce516ac88..06ef8face89b 100644 --- a/client-python/pycti/entities/opencti_threat_actor.py +++ b/client-python/pycti/entities/opencti_threat_actor.py @@ -2,6 +2,7 @@ import json import uuid +import warnings from typing import Union from stix2.canonicalization.Canonicalize import canonicalize @@ -13,12 +14,18 @@ class ThreatActor: """Main ThreatActor class for OpenCTI + Manages threat actor entities (groups and individuals) in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): - """Create an instance of ThreatActor""" + """Initialize the ThreatActor instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.threat_actor_group = ThreatActorGroup(opencti) self.threat_actor_individual = ThreatActorIndividual(opencti) @@ -143,12 +150,28 @@ def __init__(self, opencti): @staticmethod def generate_id(name, opencti_type): + """Generate a STIX ID for a Threat Actor. + + :param name: the name of the Threat Actor + :type name: str + :param opencti_type: the type of the Threat Actor (e.g., 'Threat-Actor-Group') + :type opencti_type: str + :return: STIX ID for the Threat Actor + :rtype: str + """ data = {"name": name.lower().strip(), "opencti_type": opencti_type} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) return "threat-actor--" + id def generate_id_from_data(self, data): + """Generate a STIX ID from Threat Actor data. + + :param data: Dictionary containing 'name' and optionally 'x_opencti_type' keys + :type data: dict + :return: STIX ID for the Threat Actor + :rtype: str + """ data_type = "Threat-Actor-Group" if "x_opencti_type" in data: data_type = data["x_opencti_type"] @@ -157,18 +180,28 @@ def generate_id_from_data(self, data): return ThreatActor.generate_id(data["name"], data_type) def list(self, **kwargs) -> dict: - """List Threat-Actor objects - - The list method accepts the following kwargs: + """List Threat-Actor objects. - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID - or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param bool withPagination: (optional) switch to use pagination + :param filters: the filters to apply + :type filters: dict + :param search: the search keyword + :type search: str + :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Threat-Actor objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -244,17 +277,16 @@ def list(self, **kwargs) -> dict: ) def read(self, **kwargs) -> Union[dict, None]: - """Read a Threat-Actor object - - read can be either used with a known OpenCTI entity `id` or by using a - valid filter to search and return a single Threat-Actor entity or None. - - The list method accepts the following kwargs. - - Note: either `id` or `filters` is required. + """Read a Threat-Actor object. - :param str id: the id of the Threat-Actor - :param list filters: the filters to apply if no id provided + :param id: the id of the Threat-Actor + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Threat-Actor object + :rtype: dict or None """ id = kwargs.get("id", None) @@ -291,19 +323,28 @@ def read(self, **kwargs) -> Union[dict, None]: ) return None - @DeprecationWarning def create(self, **kwargs): - # For backward compatibility, please use threat_actor_group or threat_actor_individual - return self.threat_actor_group.create(**kwargs) - - """ - Import an Threat-Actor object from a STIX2 object + """Create a Threat-Actor-Group object (deprecated). - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + .. deprecated:: + Use :meth:`threat_actor_group.create` or :meth:`threat_actor_individual.create` instead. + """ + warnings.warn( + "ThreatActor.create() is deprecated, use threat_actor_group.create() " + "or threat_actor_individual.create() instead", + DeprecationWarning, + stacklevel=2, + ) + return self.threat_actor_group.create(**kwargs) def import_from_stix2(self, **kwargs): + """Import a Threat-Actor object from a STIX2 object. + + :param stixObject: the STIX2 Threat-Actor object + :type stixObject: dict + :return: Threat-Actor object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) if "x_opencti_type" in stix_object: type = stix_object["x_opencti_type"].lower() diff --git a/client-python/pycti/entities/opencti_threat_actor_group.py b/client-python/pycti/entities/opencti_threat_actor_group.py index 358e4674db51..38576ce8b12b 100644 --- a/client-python/pycti/entities/opencti_threat_actor_group.py +++ b/client-python/pycti/entities/opencti_threat_actor_group.py @@ -10,12 +10,18 @@ class ThreatActorGroup: """Main ThreatActorGroup class for OpenCTI + Manages threat actor group entities in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): - """Create an instance of ThreatActorGroup""" + """Initialize the ThreatActorGroup instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -147,6 +153,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Threat Actor Group. + + :param name: The name of the threat actor group + :type name: str + :return: STIX ID for the threat actor group + :rtype: str + """ name = name.lower().strip() data = {"name": name, "opencti_type": "Threat-Actor-Group"} data = canonicalize(data, utf8=False) @@ -155,21 +168,35 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from threat actor group data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the threat actor group + :rtype: str + """ return ThreatActorGroup.generate_id(data["name"]) def list(self, **kwargs) -> dict: """List Threat-Actor-Group objects - The list method accepts the following kwargs: - - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID + :param filters: (optional) the filters to apply + :type filters: list + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param bool withPagination: (optional) switch to use pagination + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "`asc`" or "`desc`" + :type orderMode: str + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :return: List of Threat-Actor-Group objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -224,7 +251,7 @@ def list(self, **kwargs) -> dict: final_data = final_data + data while result["data"]["threatActorsGroup"]["pageInfo"]["hasNextPage"]: after = result["data"]["threatActorsGroup"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing threatActorsGroup", {"after": after} ) result = self.opencti.query( @@ -254,12 +281,14 @@ def read(self, **kwargs) -> Union[dict, None]: read can be either used with a known OpenCTI entity `id` or by using a valid filter to search and return a single Threat-Actor-Group entity or None. - The list method accepts the following kwargs. - Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Group - :param list filters: the filters to apply if no id provided + :param id: the id of the Threat-Actor-Group + :type id: str + :param filters: the filters to apply if no id provided + :type filters: list + :return: Threat-Actor-Group object + :rtype: dict or None """ id = kwargs.get("id", None) @@ -305,34 +334,62 @@ def create(self, **kwargs): By setting `update` to `True` it acts like an upsert and updates fields of an existing Threat-Actor-Group entity. - The create method accepts the following kwargs. - Note: `name` and `description` or `stix_id` is required. - :param str stix_id: stix2 id reference for the Threat-Actor-Group entity - :param str createdBy: (optional) id of the organization that created the knowledge - :param list objectMarking: (optional) list of OpenCTI markin definition ids - :param list objectLabel: (optional) list of OpenCTI label ids - :param list externalReferences: (optional) list of OpenCTI external references ids - :param bool revoked: is this entity revoked - :param int confidence: confidence level - :param str lang: language - :param str created: (optional) date in OpenCTI date format - :param str modified: (optional) date in OpenCTI date format - :param str name: name of the threat actor group - :param str description: description of the threat actor group - :param list aliases: (optional) list of alias names for the Threat-Actor-Group - :param list threat_actor_types: (optional) list of threat actor types - :param str first_seen: (optional) date in OpenCTI date format - :param str last_seen: (optional) date in OpenCTI date format - :param list roles: (optional) list of roles - :param list goals: (optional) list of goals - :param str sophistication: (optional) describe the actors sophistication in text - :param str resource_level: (optional) describe the actors resource_level in text - :param str primary_motivation: (optional) describe the actors primary_motivation in text - :param list secondary_motivations: (optional) describe the actors secondary_motivations in list of string - :param list personal_motivations: (optional) describe the actors personal_motivations in list of strings - :param bool update: (optional) choose to updated an existing Threat-Actor-Group entity, default `False` + :param stix_id: stix2 id reference for the Threat-Actor-Group entity + :type stix_id: str + :param createdBy: (optional) id of the organization that created the knowledge + :type createdBy: str + :param objectMarking: (optional) list of OpenCTI marking definition ids + :type objectMarking: list + :param objectLabel: (optional) list of OpenCTI label ids + :type objectLabel: list + :param externalReferences: (optional) list of OpenCTI external references ids + :type externalReferences: list + :param revoked: is this entity revoked + :type revoked: bool + :param confidence: confidence level + :type confidence: int + :param lang: language + :type lang: str + :param created: (optional) date in OpenCTI date format + :type created: str + :param modified: (optional) date in OpenCTI date format + :type modified: str + :param name: name of the threat actor group + :type name: str + :param description: description of the threat actor group + :type description: str + :param aliases: (optional) list of alias names for the Threat-Actor-Group + :type aliases: list + :param threat_actor_types: (optional) list of threat actor types + :type threat_actor_types: list + :param first_seen: (optional) date in OpenCTI date format + :type first_seen: str + :param last_seen: (optional) date in OpenCTI date format + :type last_seen: str + :param roles: (optional) list of roles + :type roles: list + :param goals: (optional) list of goals + :type goals: list + :param sophistication: (optional) describe the actors sophistication in text + :type sophistication: str + :param resource_level: (optional) describe the actors resource_level in text + :type resource_level: str + :param primary_motivation: (optional) describe the actors primary_motivation in text + :type primary_motivation: str + :param secondary_motivations: (optional) describe the actors secondary_motivations in list of string + :type secondary_motivations: list + :param personal_motivations: (optional) describe the actors personal_motivations in list of strings + :type personal_motivations: list + :param update: (optional) choose to updated an existing Threat-Actor-Group entity, default `False` + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Threat-Actor-Group object + :rtype: dict or None """ stix_id = kwargs.get("stix_id", None) @@ -351,6 +408,7 @@ def create(self, **kwargs): threat_actor_types = kwargs.get("threat_actor_types", None) first_seen = kwargs.get("first_seen", None) last_seen = kwargs.get("last_seen", None) + roles = kwargs.get("roles", None) goals = kwargs.get("goals", None) sophistication = kwargs.get("sophistication", None) resource_level = kwargs.get("resource_level", None) @@ -362,6 +420,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Threat-Actor-Group", {"name": name}) @@ -396,6 +457,7 @@ def create(self, **kwargs): "threat_actor_types": threat_actor_types, "first_seen": first_seen, "last_seen": last_seen, + "roles": roles, "goals": goals, "sophistication": sophistication, "resource_level": resource_level, @@ -406,6 +468,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -414,17 +479,19 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_threat_actor_group] Missing parameters: name and description" + "[opencti_threat_actor_group] Missing parameters: name" ) - - """ - Import an Threat-Actor-Group object from a STIX2 object - - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Threat Actor Group object from a STIX2 object. + + :param stixObject: the STIX2 Threat Actor object + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :param update: whether to update if the entity already exists + :return: Threat Actor Group object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -446,6 +513,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -490,6 +563,7 @@ def import_from_stix2(self, **kwargs): last_seen=( stix_object["last_seen"] if "last_seen" in stix_object else None ), + roles=stix_object["roles"] if "roles" in stix_object else None, goals=stix_object["goals"] if "goals" in stix_object else None, sophistication=( stix_object["sophistication"] @@ -537,8 +611,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_threat_actor_group] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_threat_actor_individual.py b/client-python/pycti/entities/opencti_threat_actor_individual.py index 8b994c1b2e3a..c03570acbcee 100644 --- a/client-python/pycti/entities/opencti_threat_actor_individual.py +++ b/client-python/pycti/entities/opencti_threat_actor_individual.py @@ -10,12 +10,18 @@ class ThreatActorIndividual: """Main ThreatActorIndividual class for OpenCTI + Manages individual threat actor entities in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): - """Create an instance of ThreatActorIndividual""" + """Initialize the ThreatActorIndividual instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -147,6 +153,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Threat Actor Individual. + + :param name: The name of the threat actor individual + :type name: str + :return: STIX ID for the threat actor individual + :rtype: str + """ name = name.lower().strip() data = {"name": name, "opencti_type": "Threat-Actor-Individual"} data = canonicalize(data, utf8=False) @@ -155,6 +168,13 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from threat actor individual data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the threat actor individual + :rtype: str + """ return ThreatActorIndividual.generate_id(data["name"]) def list(self, **kwargs) -> dict: @@ -162,14 +182,23 @@ def list(self, **kwargs) -> dict: The list method accepts the following kwargs: - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID + :param filters: (optional) the filters to apply + :type filters: list + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param bool withPagination: (optional) switch to use pagination + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "`asc`" or "`desc`" + :type orderMode: str + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :return: List of Threat-Actor-Individual objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -229,7 +258,7 @@ def list(self, **kwargs) -> dict: after = result["data"]["threatActorsIndividuals"]["pageInfo"][ "endCursor" ] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing threatActorsIndividuals", {"after": after} ) result = self.opencti.query( @@ -257,14 +286,16 @@ def read(self, **kwargs) -> Union[dict, None]: """Read a Threat-Actor-Individual object read can be either used with a known OpenCTI entity `id` or by using a - valid filter to search and return a single Threat-Actor-Group entity or None. - - The list method accepts the following kwargs. + valid filter to search and return a single Threat-Actor-Individual entity or None. Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Individual - :param list filters: the filters to apply if no id provided + :param id: the id of the Threat-Actor-Individual + :type id: str + :param filters: the filters to apply if no id provided + :type filters: list + :return: Threat-Actor-Individual object + :rtype: dict or None """ id = kwargs.get("id", None) @@ -310,34 +341,62 @@ def create(self, **kwargs): By setting `update` to `True` it acts like an upsert and updates fields of an existing Threat-Actor-Individual entity. - The create method accepts the following kwargs. - Note: `name` and `description` or `stix_id` is required. - :param str stix_id: stix2 id reference for the Threat-Actor-Individual entity - :param str createdBy: (optional) id of the organization that created the knowledge - :param list objectMarking: (optional) list of OpenCTI markin definition ids - :param list objectLabel: (optional) list of OpenCTI label ids - :param list externalReferences: (optional) list of OpenCTI external references ids - :param bool revoked: is this entity revoked - :param int confidence: confidence level - :param str lang: language - :param str created: (optional) date in OpenCTI date format - :param str modified: (optional) date in OpenCTI date format - :param str name: name of the threat actor individual - :param str description: description of the threat actor individual - :param list aliases: (optional) list of alias names for the Threat-Actor-Individual - :param list threat_actor_types: (optional) list of threat actor types - :param str first_seen: (optional) date in OpenCTI date format - :param str last_seen: (optional) date in OpenCTI date format - :param list roles: (optional) list of roles - :param list goals: (optional) list of goals - :param str sophistication: (optional) describe the actors sophistication in text - :param str resource_level: (optional) describe the actors resource_level in text - :param str primary_motivation: (optional) describe the actors primary_motivation in text - :param list secondary_motivations: (optional) describe the actors secondary_motivations in list of string - :param list personal_motivations: (optional) describe the actors personal_motivations in list of strings - :param bool update: (optional) choose to updated an existing Threat-Actor-Individual entity, default `False` + :param stix_id: stix2 id reference for the Threat-Actor-Individual entity + :type stix_id: str + :param createdBy: (optional) id of the organization that created the knowledge + :type createdBy: str + :param objectMarking: (optional) list of OpenCTI marking definition ids + :type objectMarking: list + :param objectLabel: (optional) list of OpenCTI label ids + :type objectLabel: list + :param externalReferences: (optional) list of OpenCTI external references ids + :type externalReferences: list + :param revoked: is this entity revoked + :type revoked: bool + :param confidence: confidence level + :type confidence: int + :param lang: language + :type lang: str + :param created: (optional) date in OpenCTI date format + :type created: str + :param modified: (optional) date in OpenCTI date format + :type modified: str + :param name: name of the threat actor individual + :type name: str + :param description: description of the threat actor individual + :type description: str + :param aliases: (optional) list of alias names for the Threat-Actor-Individual + :type aliases: list + :param threat_actor_types: (optional) list of threat actor types + :type threat_actor_types: list + :param first_seen: (optional) date in OpenCTI date format + :type first_seen: str + :param last_seen: (optional) date in OpenCTI date format + :type last_seen: str + :param roles: (optional) list of roles + :type roles: list + :param goals: (optional) list of goals + :type goals: list + :param sophistication: (optional) describe the actors sophistication in text + :type sophistication: str + :param resource_level: (optional) describe the actors resource_level in text + :type resource_level: str + :param primary_motivation: (optional) describe the actors primary_motivation in text + :type primary_motivation: str + :param secondary_motivations: (optional) describe the actors secondary_motivations in list of string + :type secondary_motivations: list + :param personal_motivations: (optional) describe the actors personal_motivations in list of strings + :type personal_motivations: list + :param update: (optional) choose to updated an existing Threat-Actor-Individual entity, default `False` + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Threat-Actor-Individual object + :rtype: dict or None """ stix_id = kwargs.get("stix_id", None) @@ -356,6 +415,7 @@ def create(self, **kwargs): threat_actor_types = kwargs.get("threat_actor_types", None) first_seen = kwargs.get("first_seen", None) last_seen = kwargs.get("last_seen", None) + roles = kwargs.get("roles", None) goals = kwargs.get("goals", None) sophistication = kwargs.get("sophistication", None) resource_level = kwargs.get("resource_level", None) @@ -367,6 +427,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info( @@ -403,6 +466,7 @@ def create(self, **kwargs): "threat_actor_types": threat_actor_types, "first_seen": first_seen, "last_seen": last_seen, + "roles": roles, "goals": goals, "sophistication": sophistication, "resource_level": resource_level, @@ -413,6 +477,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -421,17 +488,19 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_threat_actor_individual] Missing parameters: name and description" + "[opencti_threat_actor_individual] Missing parameters: name" ) - - """ - Import an Threat-Actor-Individual object from a STIX2 object - - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Threat-Actor-Individual object from a STIX2 object. + + :param stixObject: the STIX2 Threat-Actor-Individual object + :param extras: extra parameters including created_by_id, object_marking_ids, files, filesMarkings, etc. + :param update: whether to update if the entity already exists + :return: Threat-Actor-Individual object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -453,7 +522,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], createdBy=( @@ -497,6 +571,7 @@ def import_from_stix2(self, **kwargs): last_seen=( stix_object["last_seen"] if "last_seen" in stix_object else None ), + roles=stix_object["roles"] if "roles" in stix_object else None, goals=stix_object["goals"] if "goals" in stix_object else None, sophistication=( stix_object["sophistication"] @@ -544,8 +619,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_threat_actor_individual] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_tool.py b/client-python/pycti/entities/opencti_tool.py index 17db51738c8b..9101d041e3ae 100644 --- a/client-python/pycti/entities/opencti_tool.py +++ b/client-python/pycti/entities/opencti_tool.py @@ -12,9 +12,15 @@ class Tool: Manages tools used by threat actors in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Tool instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -171,14 +182,23 @@ def list(self, **kwargs): """List Tool objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :return: List of Tool objects :rtype: list """ @@ -258,8 +278,11 @@ def read(self, **kwargs): """Read a Tool object. :param id: the id of the Tool + :type id: str :param filters: the filters to apply if no id provided + :type filters: dict :param customAttributes: custom attributes to return + :type customAttributes: str :return: Tool object :rtype: dict or None """ @@ -300,17 +323,52 @@ def read(self, **kwargs): def create(self, **kwargs): """Create a Tool object. - :param name: the name of the Tool + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Tool (required) + :type name: str :param description: description of the tool + :type description: str :param aliases: list of aliases + :type aliases: list :param tool_types: types of tool + :type tool_types: list :param tool_version: version of the tool + :type tool_version: str :param killChainPhases: kill chain phases - :param createdBy: creator identity - :param objectMarking: marking definitions - :param objectLabel: labels - :param externalReferences: external references + :type killChainPhases: list + :param createdBy: creator identity ID + :type createdBy: str + :param objectMarking: marking definition IDs + :type objectMarking: list + :param objectLabel: label IDs + :type objectLabel: list + :param externalReferences: external reference IDs + :type externalReferences: list + :param objectOrganization: organization IDs + :type objectOrganization: list + :param revoked: whether the tool is revoked + :type revoked: bool + :param confidence: confidence level (0-100) + :type confidence: int + :param lang: language + :type lang: str + :param created: creation date + :type created: str + :param modified: modification date + :type modified: str + :param x_opencti_stix_ids: additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date + :type x_opencti_modified_at: str :param update: whether to update existing tool + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list :return: Tool object :rtype: dict or None """ @@ -335,6 +393,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Tool", {"name": name}) @@ -373,23 +434,29 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) return self.opencti.process_multiple_fields(result["data"]["toolAdd"]) else: - self.opencti.app_logger.error( - "[opencti_tool] Missing parameters: name and description" - ) - - """ - Import an Tool object from a STIX2 object - - :param stixObject: the Stix-Object Tool - :return Tool object - """ + self.opencti.app_logger.error("[opencti_tool] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Tool object from a STIX2 object. + + :param stixObject: the STIX2 Tool object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Tool object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -411,8 +478,14 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) - return self.opencti.tool.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -478,8 +551,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_tool] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_user.py b/client-python/pycti/entities/opencti_user.py index 7bc7ee877ffe..801c382c6bab 100644 --- a/client-python/pycti/entities/opencti_user.py +++ b/client-python/pycti/entities/opencti_user.py @@ -15,9 +15,17 @@ class User: You can view the properties, token_properties, session_properties, and me_properties attributes of a User object to view what attributes will be present in a User or MeUser object. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the User instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -352,7 +360,7 @@ def read(self, **kwargs) -> Optional[Dict]: ) else: self.opencti.admin_logger.error( - "[opencti_user] Missing paramters: id, search, or filters" + "[opencti_user] Missing parameters: id, search, or filters" ) return None @@ -654,7 +662,7 @@ def delete_membership(self, **kwargs) -> Optional[Dict]: return None self.opencti.admin_logger.info( - "Removing used from group", {"id": id, "group_id": group_id} + "Removing user from group", {"id": id, "group_id": group_id} ) query = ( """ @@ -690,6 +698,7 @@ def add_organization(self, **kwargs) -> Optional[Dict]: self.opencti.admin_logger.error( "[opencti_user] Missing parameters: id and organization_id" ) + return None self.opencti.admin_logger.info( "Adding user to organization", @@ -731,6 +740,7 @@ def delete_organization(self, **kwargs) -> Optional[Dict]: self.opencti.admin_logger.error( "[opencti_user] Missing parameters: id and organization_id" ) + return None self.opencti.admin_logger.info( "Removing user from organization", @@ -794,12 +804,21 @@ def token_renew(self, **kwargs) -> Optional[Dict]: ) def send_mail(self, **kwargs): + """Send an email to a user using a template. + + :param id: the user ID to send the email to + :type id: str + :param template_id: the email template ID to use + :type template_id: str + :return: None + """ id = kwargs.get("id", None) template_id = kwargs.get("template_id", None) if id is None or template_id is None: self.opencti.admin_logger.error( "[opencti_user] Missing parameters: id and template_id" ) + return None self.opencti.admin_logger.info( "Send email to user", {"id": id, "template_id": template_id} @@ -816,6 +835,13 @@ def send_mail(self, **kwargs): self.opencti.query(query, {"input": input}) def process_multiple_fields(self, data): + """Process and normalize fields in user data. + + :param data: the user data dictionary to process + :type data: dict + :return: the processed user data with normalized fields + :rtype: dict + """ if "roles" in data: data["roles"] = self.opencti.process_multiple(data["roles"]) data["rolesIds"] = self.opencti.process_multiple_ids(data["roles"]) diff --git a/client-python/pycti/entities/opencti_vocabulary.py b/client-python/pycti/entities/opencti_vocabulary.py index dc090300ae1e..8ae69b4aa92c 100644 --- a/client-python/pycti/entities/opencti_vocabulary.py +++ b/client-python/pycti/entities/opencti_vocabulary.py @@ -10,9 +10,15 @@ class Vocabulary: Manages vocabularies and controlled vocabularies in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Vocabulary instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -27,6 +33,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, category): + """Generate a STIX ID for a Vocabulary. + + :param name: the name of the Vocabulary + :type name: str + :param category: the category of the Vocabulary + :type category: str + :return: STIX ID for the Vocabulary + :rtype: str + """ name = name.lower().strip() data = {"name": name, "category": category} data = canonicalize(data, utf8=False) @@ -35,9 +50,23 @@ def generate_id(name, category): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Vocabulary data. + + :param data: Dictionary containing 'name' and 'category' keys + :type data: dict + :return: STIX ID for the Vocabulary + :rtype: str + """ return Vocabulary.generate_id(data["name"], data["category"]) def list(self, **kwargs): + """List Vocabulary objects. + + :param filters: the filters to apply + :type filters: dict + :return: List of Vocabulary objects + :rtype: list + """ filters = kwargs.get("filters", None) self.opencti.app_logger.info( "Listing Vocabularies with filters", {"filters": json.dumps(filters)} @@ -66,6 +95,15 @@ def list(self, **kwargs): return self.opencti.process_multiple(result["data"]["vocabularies"]) def read(self, **kwargs): + """Read a Vocabulary object. + + :param id: the id of the Vocabulary + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :return: Vocabulary object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -96,6 +134,17 @@ def read(self, **kwargs): return None def read_or_create_unchecked_with_cache(self, vocab, cache, field): + """Read or create a Vocabulary using a cache for optimization. + + :param vocab: the vocabulary name + :type vocab: str + :param cache: the cache dictionary + :type cache: dict + :param field: the field configuration containing 'required' and 'key' + :type field: dict + :return: Vocabulary object or None + :rtype: dict or None + """ if "vocab_" + vocab in cache: vocab_data = cache["vocab_" + vocab] else: @@ -109,6 +158,29 @@ def read_or_create_unchecked_with_cache(self, vocab, cache, field): return vocab_data def create(self, **kwargs): + """Create a Vocabulary object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Vocabulary (required) + :type name: str + :param category: the category of the Vocabulary (required) + :type category: str + :param description: (optional) description + :type description: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Vocabulary object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) category = kwargs.get("category", None) @@ -155,8 +227,22 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_vocabulary] Missing parameters: name or category", ) + return None def read_or_create_unchecked(self, **kwargs): + """Read or create a Vocabulary. + + If the user has no rights to create the vocabulary, return None. + + :param name: the vocabulary name + :type name: str + :param required: whether the vocabulary is required + :type required: bool + :param category: the category of the vocabulary + :type category: str + :return: The available or created Vocabulary object + :rtype: dict or None + """ value = kwargs.get("name", None) vocab = self.read( filters={ @@ -173,13 +259,22 @@ def read_or_create_unchecked(self, **kwargs): return vocab def update_field(self, **kwargs): + """Update a Vocabulary object field. + + :param id: the Vocabulary id + :type id: str + :param input: the input of the field + :type input: list + :return: The updated Vocabulary object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: self.opencti.app_logger.info("Updating Vocabulary", {"id": id}) query = """ mutation VocabularyEdit($id: ID!, $input: [EditInput!]!) { - vocabularyFieldPatch(id: $id, input: $input) { + vocabularyFieldPatch(id: $id, input: $input) { id standard_id entity_type @@ -198,6 +293,6 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_vocabulary] Missing parameters: id and key and value" + "[opencti_vocabulary] Missing parameters: id and input" ) return None diff --git a/client-python/pycti/entities/opencti_vulnerability.py b/client-python/pycti/entities/opencti_vulnerability.py index ba3dc99ae915..9803adc34854 100644 --- a/client-python/pycti/entities/opencti_vulnerability.py +++ b/client-python/pycti/entities/opencti_vulnerability.py @@ -12,9 +12,15 @@ class Vulnerability: Manages vulnerability information including CVE data in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Vulnerability instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -179,6 +190,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Vulnerability. + + :param name: The name of the vulnerability (e.g., CVE ID) + :type name: str + :return: STIX ID for the vulnerability + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -187,19 +205,39 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from vulnerability data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the vulnerability + :rtype: str + """ return Vulnerability.generate_id(data["name"]) - """ - List Vulnerability objects + def list(self, **kwargs): + """List Vulnerability objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Vulnerability objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Vulnerability objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -253,7 +291,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["vulnerabilities"]["pageInfo"]["hasNextPage"]: after = result["data"]["vulnerabilities"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Vulnerabilities", {"after": after} ) result = self.opencti.query( @@ -275,15 +313,18 @@ def list(self, **kwargs): result["data"]["vulnerabilities"], with_pagination ) - """ - Read a Vulnerability object + def read(self, **kwargs): + """Read a Vulnerability object. :param id: the id of the Vulnerability + :type id: str :param filters: the filters to apply if no id provided - :return Vulnerability object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Vulnerability object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -314,18 +355,74 @@ def read(self, **kwargs): return None else: self.opencti.app_logger.error( - "[opencti_tool] Missing parameters: id or filters" + "[opencti_vulnerability] Missing parameters: id or filters" ) return None - """ - Create a Vulnerability object - - :param name: the name of the Vulnerability - :return Vulnerability object - """ - def create(self, **kwargs): + """Create a Vulnerability object. + + :param name: the name of the Vulnerability (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the vulnerability is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_opencti_cvss_vector_string: (optional) CVSS v3 vector string + :type x_opencti_cvss_vector_string: str + :param x_opencti_cvss_base_score: (optional) CVSS v3 base score + :type x_opencti_cvss_base_score: float + :param x_opencti_cvss_base_severity: (optional) CVSS v3 base severity + :type x_opencti_cvss_base_severity: str + :param x_opencti_cwe: (optional) CWE ID + :type x_opencti_cwe: str + :param x_opencti_cisa_kev: (optional) CISA KEV flag + :type x_opencti_cisa_kev: bool + :param x_opencti_epss_score: (optional) EPSS score + :type x_opencti_epss_score: float + :param x_opencti_epss_percentile: (optional) EPSS percentile + :type x_opencti_epss_percentile: float + :param x_opencti_score: (optional) OpenCTI score + :type x_opencti_score: int + :param x_opencti_first_seen_active: (optional) first seen active date + :type x_opencti_first_seen_active: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param files: (optional) list of File objects to attach + :type files: list + :param filesMarkings: (optional) list of lists of marking definition IDs for each file + :type filesMarkings: list + :return: Vulnerability object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -466,6 +563,9 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + files = kwargs.get("files", None) + files_markings = kwargs.get("filesMarkings", None) + upsert_operations = kwargs.get("upsert_operations", None) if name is not None: self.opencti.app_logger.info("Creating Vulnerability", {"name": name}) @@ -553,6 +653,9 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "files": files, + "filesMarkings": files_markings, + "upsertOperations": upsert_operations, } }, ) @@ -561,22 +664,40 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_vulnerability] Missing parameters: name and description" + "[opencti_vulnerability] Missing parameters: name" ) - - """ - Import an Vulnerability object from a STIX2 object - - :param stixObject: the Stix-Object Vulnerability - :return Vulnerability object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Vulnerability object from a STIX2 object. + + :param stixObject: the STIX2 Vulnerability object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Vulnerability object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) if stix_object is not None: + # Search in extensions + if "x_opencti_stix_ids" not in stix_object: + stix_object["x_opencti_stix_ids"] = ( + self.opencti.get_attribute_in_extension("stix_ids", stix_object) + ) + if "x_opencti_granted_refs" not in stix_object: + stix_object["x_opencti_granted_refs"] = ( + self.opencti.get_attribute_in_extension("granted_refs", stix_object) + ) + if "x_opencti_workflow_id" not in stix_object: + stix_object["x_opencti_workflow_id"] = ( + self.opencti.get_attribute_in_extension("workflow_id", stix_object) + ) # Backward compatibility if "x_opencti_base_score" in stix_object: stix_object["x_opencti_cvss_base_score"] = stix_object[ @@ -849,6 +970,12 @@ def import_from_stix2(self, **kwargs): "cvss_v4_availability_impact_v", stix_object ) ) + if "x_opencti_cvss_v4_availability_impact_s" not in stix_object: + stix_object["x_opencti_cvss_v4_availability_impact_s"] = ( + self.opencti.get_attribute_in_extension( + "cvss_v4_availability_impact_s", stix_object + ) + ) if "x_opencti_cvss_v4_exploit_maturity" not in stix_object: stix_object["x_opencti_cvss_v4_exploit_maturity"] = ( self.opencti.get_attribute_in_extension( @@ -889,6 +1016,12 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "opencti_upsert_operations" not in stix_object: + stix_object["opencti_upsert_operations"] = ( + self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) + ) return self.create( stix_id=stix_object["id"], @@ -1191,8 +1324,16 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=( + stix_object["opencti_upsert_operations"] + if "opencti_upsert_operations" in stix_object + else None + ), ) else: self.opencti.app_logger.error( "[opencti_vulnerability] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py index aad2291c2de8..e3a4a30b09eb 100644 --- a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py +++ b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py @@ -2,12 +2,9 @@ class StixCyberObservableDeprecatedMixin: - """ - deprecated [>=6.2 & <6.8]` - Promote a Stix-Observable to an Indicator + """Deprecated mixin for Stix-Cyber-Observable promotion [>=6.2 & <6.8]. - :param id: the Stix-Observable id - :return the observable + Contains deprecated method to promote a Stix-Observable to an Indicator. """ @deprecation.deprecated( @@ -16,6 +13,20 @@ class StixCyberObservableDeprecatedMixin: details="Use promote_to_indicator_v2 instead.", ) def promote_to_indicator(self, **kwargs): + """Promote a Stix-Cyber-Observable to an Indicator (deprecated). + + .. deprecated:: 6.2 + Use :meth:`promote_to_indicator_v2` instead. + + :param id: the id of the Stix-Cyber-Observable + :type id: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Indicator object + :rtype: dict or None + """ id = kwargs.get("id", None) custom_attributes = kwargs.get("customAttributes", None) with_files = kwargs.get("withFiles", False) diff --git a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py index 72569b6387f3..1a5fec0bac17 100644 --- a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py +++ b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py @@ -468,6 +468,10 @@ id name size + metaData { + mimetype + version + } } } } diff --git a/client-python/pycti/utils/constants.py b/client-python/pycti/utils/constants.py index e1f577af2d04..7264ddf24023 100644 --- a/client-python/pycti/utils/constants.py +++ b/client-python/pycti/utils/constants.py @@ -12,7 +12,25 @@ from stix2.utils import NOW -class StixCyberObservableTypes(Enum): +class CaseInsensitiveEnum(Enum): + """Base Enum class with case-insensitive value lookup.""" + + @classmethod + def has_value(cls, value: str) -> bool: + """Check if the enum contains the given value (case-insensitive). + + :param value: Value to check + :type value: str + :return: True if value exists in enum, False otherwise + :rtype: bool + """ + lower_values = [v.lower() for v in cls._value2member_map_] + return value.lower() in lower_values + + +class StixCyberObservableTypes(CaseInsensitiveEnum): + """Enumeration of STIX Cyber Observable types supported by OpenCTI.""" + AUTONOMOUS_SYSTEM = "Autonomous-System" DIRECTORY = "Directory" DOMAIN_NAME = "Domain-Name" @@ -48,49 +66,37 @@ class StixCyberObservableTypes(Enum): PERSONA = "Persona" SSH_KEY = "SSH-Key" - @classmethod - def has_value(cls, value: str) -> bool: - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class IdentityTypes(CaseInsensitiveEnum): + """Enumeration of Identity types supported by OpenCTI.""" -class IdentityTypes(Enum): SECTOR = "Sector" ORGANIZATION = "Organization" INDIVIDUAL = "Individual" SYSTEM = "System" SECURITYPLATFORM = "SecurityPlatform" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class ThreatActorTypes(CaseInsensitiveEnum): + """Enumeration of Threat Actor types supported by OpenCTI.""" -class ThreatActorTypes(Enum): THREAT_ACTOR_GROUP = "Threat-Actor-Group" THREAT_ACTOR_INDIVIDUAL = "Threat-Actor-Individual" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class LocationTypes(CaseInsensitiveEnum): + """Enumeration of Location types supported by OpenCTI.""" -class LocationTypes(Enum): REGION = "Region" COUNTRY = "Country" ADMINISTRATIVE_AREA = "Administrative-Area" CITY = "City" POSITION = "Position" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class ContainerTypes(CaseInsensitiveEnum): + """Enumeration of Container types supported by OpenCTI.""" -class ContainerTypes(Enum): NOTE = "Note" OBSERVED_DATA = "Observed-Data" OPINION = "Opinion" @@ -98,25 +104,19 @@ class ContainerTypes(Enum): GROUPING = "Grouping" CASE = "Case" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class StixMetaTypes(CaseInsensitiveEnum): + """Enumeration of STIX Meta Object types supported by OpenCTI.""" -class StixMetaTypes(Enum): MARKING_DEFINITION = "Marking-Definition" LABEL = "Label" EXTERNAL_REFERENCE = "External-Reference" KILL_CHAIN_PHASE = "Kill-Chain-Phase" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class MultipleRefRelationship(CaseInsensitiveEnum): + """Enumeration of relationship types that can have multiple references.""" -class MultipleRefRelationship(Enum): OPERATING_SYSTEM = "operating-system" SAMPLE = "sample" CONTAINS = "contains" @@ -130,15 +130,10 @@ class MultipleRefRelationship(Enum): CHILD = "child" BODY_MULTIPART = "body-multipart" VALUES = "values" - SERVICE_DDL = "service-dll" + SERVICE_DLL = "service-dll" INSTALLED_SOFTWARE = "installed-software" RELATION_ANALYSIS_SCO = "analysis-sco" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr - # Custom objects @@ -164,7 +159,32 @@ def has_value(cls, value): ], ) class CustomObjectCaseIncident: - """Case-Incident object.""" + """Custom STIX2 Case-Incident object for OpenCTI. + + Represents a case-incident container with associated metadata including + name, description, severity, priority, and response types. + + :param name: Name of the case incident (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the case incident + :type description: str + :param severity: Severity level of the incident + :type severity: str + :param priority: Priority level of the incident + :type priority: str + :param response_types: List of response types + :type response_types: list + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param external_references: List of external references + :type external_references: list + :param object_refs: List of referenced STIX objects + :type object_refs: list + """ pass @@ -189,8 +209,33 @@ class CustomObjectCaseIncident: ), ], ) -class CustomObjectCaseRfit: - """Case-Rfi object.""" +class CustomObjectCaseRfi: + """Custom STIX2 Case-RFI (Request For Information) object for OpenCTI. + + Represents a request for information container with associated metadata + including name, description, severity, priority, and information types. + + :param name: Name of the RFI case (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the RFI case + :type description: str + :param severity: Severity level of the RFI + :type severity: str + :param priority: Priority level of the RFI + :type priority: str + :param information_types: List of information types requested + :type information_types: list + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param external_references: List of external references + :type external_references: list + :param object_refs: List of referenced STIX objects + :type object_refs: list + """ pass @@ -218,7 +263,26 @@ class CustomObjectCaseRfit: ], ) class CustomObjectTask: - """Task object.""" + """Custom STIX2 Task object for OpenCTI. + + Represents a task with associated metadata including name, description, + due date, and assignees. + + :param name: Name of the task (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the task + :type description: str + :param due_date: Due date timestamp for the task + :type due_date: datetime + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param object_refs: List of referenced STIX objects + :type object_refs: list + """ pass @@ -237,7 +301,28 @@ class CustomObjectTask: ], ) class CustomObjectChannel: - """Channel object.""" + """Custom STIX2 Channel object for OpenCTI. + + Represents a communication channel with associated metadata including + name, description, aliases, and channel types. + + :param name: Name of the channel (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the channel + :type description: str + :param aliases: List of alternative names for the channel + :type aliases: list + :param channel_types: List of channel types + :type channel_types: list + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param external_references: List of external references + :type external_references: list + """ pass @@ -260,7 +345,17 @@ class CustomObjectChannel: ["value"], ) class CustomObservableHostname: - """Hostname observable.""" + """Custom STIX2 Hostname observable for OpenCTI. + + Represents a hostname cyber observable with its associated value. + + :param value: The hostname value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -280,7 +375,17 @@ class CustomObservableHostname: ["value"], ) class CustomObservableText: - """Text observable.""" + """Custom STIX2 Text observable for OpenCTI. + + Represents a generic text cyber observable with its associated value. + + :param value: The text value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -304,7 +409,25 @@ class CustomObservableText: ["card_number"], ) class CustomObservablePaymentCard: - """Payment card observable.""" + """Custom STIX2 Payment Card observable for OpenCTI. + + Represents a payment card cyber observable with card details. + + :param value: Display value for the payment card (required) + :type value: str + :param card_number: The payment card number (required) + :type card_number: str + :param expiration_date: Card expiration date + :type expiration_date: str + :param cvv: Card verification value + :type cvv: str + :param holder_name: Name of the card holder + :type holder_name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -327,7 +450,23 @@ class CustomObservablePaymentCard: ["iban"], ) class CustomObservableBankAccount: - """Bank Account observable.""" + """Custom STIX2 Bank Account observable for OpenCTI. + + Represents a bank account cyber observable with account details. + + :param value: Display value for the bank account (required) + :type value: str + :param iban: International Bank Account Number (required) + :type iban: str + :param bic: Bank Identifier Code + :type bic: str + :param account_number: Bank account number + :type account_number: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -347,7 +486,17 @@ class CustomObservableBankAccount: ["value"], ) class CustomObservableCredential: - """Credential observable.""" + """Custom STIX2 Credential observable for OpenCTI. + + Represents a credential cyber observable such as a password or access token. + + :param value: The credential value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -367,7 +516,17 @@ class CustomObservableCredential: ["value"], ) class CustomObservableCryptocurrencyWallet: - """Cryptocurrency wallet observable.""" + """Custom STIX2 Cryptocurrency Wallet observable for OpenCTI. + + Represents a cryptocurrency wallet address cyber observable. + + :param value: The wallet address value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -387,7 +546,17 @@ class CustomObservableCryptocurrencyWallet: ["value"], ) class CustomObservablePhoneNumber: - """Phone number observable.""" + """Custom STIX2 Phone Number observable for OpenCTI. + + Represents a phone number cyber observable. + + :param value: The phone number value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -407,7 +576,17 @@ class CustomObservablePhoneNumber: ["value"], ) class CustomObservableTrackingNumber: - """Tracking number observable.""" + """Custom STIX2 Tracking Number observable for OpenCTI. + + Represents a tracking number cyber observable (e.g., package tracking). + + :param value: The tracking number value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -427,7 +606,17 @@ class CustomObservableTrackingNumber: ["value"], ) class CustomObservableUserAgent: - """User-Agent observable.""" + """Custom STIX2 User-Agent observable for OpenCTI. + + Represents a User-Agent string cyber observable from HTTP headers. + + :param value: The User-Agent string value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -452,7 +641,27 @@ class CustomObservableUserAgent: ["url"], ) class CustomObservableMediaContent: - """Media-Content observable.""" + """Custom STIX2 Media-Content observable for OpenCTI. + + Represents a media content cyber observable such as articles or posts. + + :param title: Title of the media content + :type title: str + :param description: Description of the media content + :type description: str + :param content: The actual content body + :type content: str + :param media_category: Category of the media + :type media_category: str + :param url: URL of the media content (required) + :type url: str + :param publication_date: Publication date timestamp + :type publication_date: datetime + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -473,7 +682,19 @@ class CustomObservableMediaContent: ["persona_name", "persona_type"], ) class CustomObservablePersona: - """Persona observable.""" + """Custom STIX2 Persona observable for OpenCTI. + + Represents a persona or online identity cyber observable. + + :param persona_name: Name of the persona (required) + :type persona_name: str + :param persona_type: Type of the persona (required) + :type persona_type: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -493,6 +714,46 @@ class CustomObservablePersona: ["value"], ) class CustomObservableCryptographicKey: - """Cryptographic-Key observable.""" + """Custom STIX2 Cryptographic-Key observable for OpenCTI. + + Represents a cryptographic key cyber observable such as API keys or encryption keys. + + :param value: The cryptographic key value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ + + pass + + +@CustomObservable( + "ssh-key", + [ + ("value", StringProperty(required=True)), + ("spec_version", StringProperty(fixed="2.1")), + ( + "object_marking_refs", + ListProperty( + ReferenceProperty(valid_types="marking-definition", spec_version="2.1") + ), + ), + ], + ["value"], +) +class CustomObservableSshKey: + """Custom STIX2 SSH-Key observable for OpenCTI. + + Represents an SSH key cyber observable such as public or private SSH keys. + + :param value: The SSH key value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass diff --git a/client-python/pycti/utils/opencti_logger.py b/client-python/pycti/utils/opencti_logger.py index e9512ca7b898..0a797395cd6a 100644 --- a/client-python/pycti/utils/opencti_logger.py +++ b/client-python/pycti/utils/opencti_logger.py @@ -14,7 +14,7 @@ def add_fields(self, log_record, record, message_dict): :param record: The LogRecord instance :param message_dict: The message dictionary """ - super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) + super().add_fields(log_record, record, message_dict) if not log_record.get("timestamp"): # This doesn't use record.created, so it is slightly off now = datetime.now(tz=timezone.utc) @@ -29,15 +29,15 @@ def logger(level, json_logging=True): """Create a logger with JSON or standard formatting. :param level: Logging level (e.g., logging.INFO, logging.DEBUG) + :type level: int :param json_logging: Whether to use JSON formatting for logs :type json_logging: bool :return: AppLogger class :rtype: class """ - # Exceptions + # Suppress verbose logging from third-party libraries logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("pika").setLevel(logging.ERROR) - # Exceptions if json_logging: log_handler = logging.StreamHandler() log_handler.setLevel(level) @@ -48,7 +48,18 @@ def logger(level, json_logging=True): logging.basicConfig(level=level) class AppLogger: + """Application logger class with metadata support. + + Provides debug, info, warning, and error logging methods with + optional metadata support for structured logging. + """ + def __init__(self, name): + """Initialize the application logger. + + :param name: Name of the logger instance + :type name: str + """ self.local_logger = logging.getLogger(name) @staticmethod @@ -69,6 +80,7 @@ def setup_logger_level(lib, log_level): :param lib: Library name :type lib: str :param log_level: Logging level to set + :type log_level: int """ logging.getLogger(lib).setLevel(log_level) diff --git a/client-python/pycti/utils/opencti_stix2.py b/client-python/pycti/utils/opencti_stix2.py index cf8dadae6230..03995266b309 100644 --- a/client-python/pycti/utils/opencti_stix2.py +++ b/client-python/pycti/utils/opencti_stix2.py @@ -1,5 +1,3 @@ -# coding: utf-8 - import base64 import datetime import json @@ -16,6 +14,7 @@ from cachetools import LRUCache from opentelemetry import metrics from requests import RequestException, Timeout +from typing_extensions import deprecated from pycti.entities.opencti_identity import Identity from pycti.utils.constants import ( @@ -38,6 +37,10 @@ datefinder.ValueError = ValueError, OverflowError utc = pytz.UTC +# For Python 3.11+, datetime.UTC is preferred over datetime.timezone.utc +# Fallback to datetime.timezone.utc for older Python versions +UTC = getattr(datetime, "UTC", datetime.timezone.utc) + # Spec version SPEC_VERSION = "2.1" ERROR_TYPE_LOCK = "LOCK_ERROR" @@ -46,10 +49,14 @@ ERROR_TYPE_DRAFT_LOCK = "DRAFT_LOCKED" ERROR_TYPE_TIMEOUT = "Request timed out" -# Extensions -STIX_EXT_OCTI = "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" -STIX_EXT_OCTI_SCO = "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" -STIX_EXT_MITRE = "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" +#: STIX Extension ID for OpenCTI custom objects and properties +STIX_EXT_OCTI: str = "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" + +#: STIX Extension ID for OpenCTI custom Cyber Observables (SCO) +STIX_EXT_OCTI_SCO: str = "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" + +#: STIX Extension ID for MITRE ATT&CK framework objects +STIX_EXT_MITRE: str = "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" PROCESSING_COUNT: int = 4 MAX_PROCESSING_COUNT: int = 100 @@ -85,24 +92,47 @@ class OpenCTIStix2: - """Python API for Stix2 in OpenCTI + """Python API for Stix2 in OpenCTI. + + Handles conversion between STIX2 format and OpenCTI internal format, + including import/export operations and bundle processing. - :param opencti: OpenCTI instance + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the OpenCTIStix2 helper. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.stix2_update = OpenCTIStix2Update(opencti) self.mapping_cache = LRUCache(maxsize=50000) self.mapping_cache_permanent = {} def get_in_cache(self, data_id): + """Get an item from the cache. + + :param data_id: ID of the data to retrieve + :type data_id: str + :return: Cached data or None if not found + :rtype: dict or None + """ api_draft_id = self.opencti.get_draft_id() if data_id + api_draft_id in self.mapping_cache: return self.mapping_cache[data_id + api_draft_id] return None def set_in_cache(self, data_id, data): + """Store an item in the cache. + + :param data_id: ID of the data to store + :type data_id: str + :param data: Data to cache + :type data: dict + """ api_draft_id = self.opencti.get_draft_id() self.mapping_cache[data_id + api_draft_id] = data @@ -119,11 +149,11 @@ def unknown_type(self, stix_object: Dict) -> None: ) def convert_markdown(self, text: str) -> str: - """converts input text to markdown style code annotation + """Convert input text to markdown style code annotation. - :param text: input text + :param text: Input text to convert :type text: str - :return: sanitized text with markdown style code annotation + :return: Sanitized text with markdown style code annotation :rtype: str """ if text is not None: @@ -132,20 +162,13 @@ def convert_markdown(self, text: str) -> str: return None def format_date(self, date: Any = None) -> str: - """Format a date to ISO 8601 string format. + """Convert multiple input date formats to OpenCTI style dates. - :param date: Date to format (various formats supported) + :param date: Input date (datetime, date, str or None) :type date: Any :return: ISO 8601 formatted date string :rtype: str """ - """converts multiple input date formats to OpenCTI style dates - - :param date: input date - :type date: Any [datetime, date, str or none] - :return: OpenCTI style date - :rtype: string - """ if isinstance(date, datetime.datetime): date_value = date elif isinstance(date, datetime.date): @@ -156,22 +179,22 @@ def format_date(self, date: Any = None) -> str: except (dateutil.parser.ParserError, TypeError, OverflowError) as e: raise ValueError(f"{e}: {date} does not contain a valid date string") else: - date_value = datetime.datetime.utcnow() + date_value = datetime.datetime.now(tz=UTC) if not date_value.tzinfo: self.opencti.app_logger.info("No timezone found. Setting to UTC") - date_value = date_value.replace(tzinfo=datetime.timezone.utc) + date_value = date_value.replace(tzinfo=UTC) return date_value.isoformat(timespec="milliseconds").replace("+00:00", "Z") def filter_objects(self, uuids: List, objects: List) -> List: - """filters objects based on UUIDs + """Filter objects based on UUIDs. - :param uuids: list of UUIDs + :param uuids: List of UUIDs to filter by :type uuids: list - :param objects: list of objects to filter + :param objects: List of objects to filter :type objects: list - :return: list of filtered objects + :return: List of filtered objects not in the uuids list :rtype: list """ @@ -183,12 +206,12 @@ def filter_objects(self, uuids: List, objects: List) -> List: return result def pick_aliases(self, stix_object: Dict) -> Optional[List]: - """check stix2 object for multiple aliases and return a list + """Check STIX2 object for multiple aliases and return a list. - :param stix_object: valid stix2 object - :type stix_object: - :return: list of aliases - :rtype: list + :param stix_object: Valid STIX2 object + :type stix_object: Dict + :return: List of aliases or None if no aliases found + :rtype: list or None """ # Add aliases @@ -208,19 +231,19 @@ def import_bundle_from_file( update: bool = False, types: List = None, ) -> Optional[Tuple[list, list]]: - """import a stix2 bundle from a file + """Import a STIX2 bundle from a file. - :param file_path: valid path to the file + :param file_path: Valid path to the file :type file_path: str - :param update: whether to updated data in the database, defaults to False + :param update: Whether to update data in the database, defaults to False :type update: bool, optional - :param types: list of stix2 types, defaults to None + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :return: list of imported stix2 objects - :rtype: List + :return: Tuple of (imported objects, failed objects) or None if file not found + :rtype: Tuple[list, list] or None """ if not os.path.isfile(file_path): - self.opencti.app_logger.error("The bundle file does not exists") + self.opencti.app_logger.error("The bundle file does not exist") return None with open(os.path.join(file_path), encoding="utf-8") as file: data = json.load(file) @@ -234,24 +257,32 @@ def import_bundle_from_json( work_id: str = None, objects_max_refs: int = 0, ) -> Tuple[list, list]: - """import a stix2 bundle from JSON data + """Import a STIX2 bundle from JSON data. - :param json_data: JSON data - :type json_data: - :param update: whether to updated data in the database, defaults to False + :param json_data: JSON data as string or bytes + :type json_data: str or bytes + :param update: Whether to update data in the database, defaults to False :type update: bool, optional - :param types: list of stix2 types, defaults to None + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :param work_id work_id: str, optional - :param objects_max_refs: max deps amount of objects, reject object import if larger than configured amount + :param work_id: Work ID for tracking import progress + :type work_id: str, optional + :param objects_max_refs: Maximum object references; rejects import if exceeded :type objects_max_refs: int, optional - :return: list of imported stix2 objects and a list of stix2 objects with too many deps - :rtype: Tuple[List,List] + :return: Tuple of (imported objects, objects with too many dependencies) + :rtype: Tuple[list, list] """ data = json.loads(json_data) return self.import_bundle(data, update, types, work_id, objects_max_refs) def resolve_author(self, title: str) -> Optional[Identity]: + """Resolve an author identity from a title string. + + :param title: Title to search for known author names + :type title: str + :return: Identity object if author found, None otherwise + :rtype: Identity or None + """ if "fireeye" in title.lower() or "mandiant" in title.lower(): return self.get_author("FireEye") if "eset" in title.lower(): @@ -293,6 +324,13 @@ def resolve_author(self, title: str) -> Optional[Identity]: return None def get_author(self, name: str) -> Identity: + """Get or create an author identity by name. + + :param name: Name of the author organization + :type name: str + :return: Identity object for the author + :rtype: Identity + """ name_in_cache = self.get_in_cache(name) if name_in_cache is not None: return name_in_cache @@ -308,13 +346,13 @@ def get_author(self, name: str) -> Identity: def extract_embedded_relationships( self, stix_object: Dict, types: List = None ) -> Dict: - """extracts embedded relationship objects from a stix2 entity + """Extract embedded relationship objects from a STIX2 entity. - :param stix_object: valid stix2 object - :type stix_object: - :param types: list of stix2 types, defaults to None + :param stix_object: Valid STIX2 object + :type stix_object: Dict + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :return: embedded relationships as dict + :return: Dictionary containing embedded relationships and references :rtype: dict """ @@ -589,6 +627,51 @@ def extract_embedded_relationships( if generated_ref_id is None: continue else: + # Collect files for external reference + ext_ref_files = [] + if "x_opencti_files" in external_reference: + ext_ref_files.extend(external_reference["x_opencti_files"]) + if ( + self.opencti.get_attribute_in_extension( + "files", external_reference + ) + is not None + ): + ext_ref_files.extend( + self.opencti.get_attribute_in_extension( + "files", external_reference + ) + ) + + # Prepare all files for upload during creation + files_to_upload = [] + files_markings = [] + for file_obj in ext_ref_files: + data = None + if "data" in file_obj: + data = base64.b64decode(file_obj["data"]) + elif "uri" in file_obj: + file_url = self.opencti.api_url.replace( + "/graphql", file_obj["uri"] + ) + data = self.opencti.fetch_opencti_file( + fetch_uri=file_url, binary=True, serialize=False + ) + if data is not None: + files_to_upload.append( + self.opencti.file( + file_obj["name"], + data, + file_obj.get( + "mime_type", "application/octet-stream" + ), + ) + ) + files_markings.append( + file_obj.get("object_marking_refs", None) + ) + + # Create external reference with all files attached external_reference_id = self.opencti.external_reference.create( source_name=source_name, url=url, @@ -598,42 +681,10 @@ def extract_embedded_relationships( if "description" in external_reference else None ), + files=files_to_upload if files_to_upload else None, + filesMarkings=files_markings if files_markings else None, )["id"] - if "x_opencti_files" in external_reference: - for file in external_reference["x_opencti_files"]: - if "data" in file: - self.opencti.external_reference.add_file( - id=external_reference_id, - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get( - "no_trigger_import", False - ), - ) - if ( - self.opencti.get_attribute_in_extension( - "files", external_reference - ) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", external_reference - ): - if "data" in file: - self.opencti.external_reference.add_file( - id=external_reference_id, - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get( - "no_trigger_import", False - ), - ) + external_references_ids.append(external_reference_id) if stix_object["type"] in [ "threat-actor", @@ -658,7 +709,7 @@ def extract_embedded_relationships( source_name, base_date=datetime.datetime.fromtimestamp(0), ) - except: + except (TypeError, OverflowError): matches = None published = None yesterday = datetime.datetime.now() - datetime.timedelta(days=1) @@ -672,7 +723,7 @@ def extract_embedded_relationships( ): published = match.strftime("%Y-%m-%dT%H:%M:%SZ") break - except: + except (TypeError, OverflowError): pass if published is None: published = default_date.strftime("%Y-%m-%dT%H:%M:%SZ") @@ -741,7 +792,7 @@ def extract_embedded_relationships( update=True, ) reports[external_reference_id] = report - except: + except Exception: self.opencti.app_logger.warning( "Cannot generate external reference" ) @@ -767,6 +818,47 @@ def extract_embedded_relationships( if generated_ref_id is None: continue else: + # Prepare all files for direct upload during creation + files_to_upload = [] + files_markings = [] + all_files = [] + if "x_opencti_files" in external_reference: + all_files = external_reference["x_opencti_files"] + elif ( + self.opencti.get_attribute_in_extension( + "files", external_reference + ) + is not None + ): + all_files = self.opencti.get_attribute_in_extension( + "files", external_reference + ) + + for file_obj in all_files: + data = None + if "data" in file_obj: + data = base64.b64decode(file_obj["data"]) + elif "uri" in file_obj: + file_url = self.opencti.api_url.replace( + "/graphql", file_obj["uri"] + ) + data = self.opencti.fetch_opencti_file( + fetch_uri=file_url, binary=True, serialize=False + ) + if data is not None: + files_to_upload.append( + self.opencti.file( + file_obj["name"], + data, + file_obj.get( + "mime_type", "application/octet-stream" + ), + ) + ) + files_markings.append( + file_obj.get("object_marking_refs", None) + ) + external_reference_id = self.opencti.external_reference.create( source_name=source_name, url=url, @@ -776,38 +868,10 @@ def extract_embedded_relationships( if "description" in external_reference else None ), + files=files_to_upload if files_to_upload else None, + filesMarkings=files_markings if files_markings else None, )["id"] - if "x_opencti_files" in external_reference: - for file in external_reference["x_opencti_files"]: - if "data" in file: - self.opencti.external_reference.add_file( - id=external_reference_id, - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) - if ( - self.opencti.get_attribute_in_extension("files", external_reference) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", external_reference - ): - if "data" in file: - self.opencti.external_reference.add_file( - id=external_reference_id, - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) + external_references_ids.append(external_reference_id) # Granted refs granted_refs_ids = [] @@ -948,6 +1012,7 @@ def get_stix_helper(self): "tool": self.opencti.tool, "vulnerability": self.opencti.vulnerability, "incident": self.opencti.incident, + "x-opencti-incident": self.opencti.incident, "marking-definition": self.opencti.marking_definition, "case-rfi": self.opencti.case_rfi, "x-opencti-case-rfi": self.opencti.case_rfi, @@ -1016,16 +1081,16 @@ def generate_standard_id_from_stix(self, data): def import_object( self, stix_object: Dict, update: bool = False, types: List = None ) -> Optional[List]: - """import a stix2 object + """Import a STIX2 object into OpenCTI. - :param stix_object: valid stix2 object - :type stix_object: - :param update: whether to updated data in the database, defaults to False + :param stix_object: Valid STIX2 object to import + :type stix_object: Dict + :param update: Whether to update data in the database, defaults to False :type update: bool, optional - :param types: list of stix2 types, defaults to None + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :return: list of imported stix2 objects - :rtype: list + :return: List of imported STIX2 objects or None on failure + :rtype: list or None """ self.opencti.app_logger.info( @@ -1045,6 +1110,37 @@ def import_object( reports = embedded_relationships["reports"] sample_refs_ids = embedded_relationships["sample_refs"] + # Extract files + x_opencti_files = [] + if "x_opencti_files" in stix_object: + x_opencti_files.extend(stix_object["x_opencti_files"]) + if self.opencti.get_attribute_in_extension("files", stix_object) is not None: + x_opencti_files.extend( + self.opencti.get_attribute_in_extension("files", stix_object) + ) + + # Prepare all files for direct upload during creation + files_to_upload = [] + files_markings = [] + for file_obj in x_opencti_files: + data = None + if "data" in file_obj: + data = base64.b64decode(file_obj["data"]) + elif "uri" in file_obj: + url = self.opencti.api_url.replace("/graphql", file_obj["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: + files_to_upload.append( + self.opencti.file( + file_obj["name"], + data, + file_obj.get("mime_type", "application/octet-stream"), + ) + ) + files_markings.append(file_obj.get("object_marking_refs", None)) + # Extra extras = { "created_by_id": created_by_id, @@ -1056,6 +1152,8 @@ def import_object( "external_references_ids": external_references_ids, "reports": reports, "sample_ids": sample_refs_ids, + "files": files_to_upload if files_to_upload else None, + "filesMarkings": files_markings if files_markings else None, } stix_helper = self.get_stix_helper().get(stix_object["type"]) @@ -1107,43 +1205,20 @@ def import_object( id=reports[external_reference_id]["id"], stixObjectOrStixRelationshipId=stix_object_result["id"], ) - # Add files - if "x_opencti_files" in stix_object: - for file in stix_object["x_opencti_files"]: - if "data" in file: - self.opencti.stix_domain_object.add_file( - id=stix_object_result["id"], - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) - if ( - self.opencti.get_attribute_in_extension("files", stix_object) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", stix_object - ): - if "data" in file: - self.opencti.stix_domain_object.add_file( - id=stix_object_result["id"], - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) return stix_object_results def import_observable( self, stix_object: Dict, update: bool = False, types: List = None ) -> None: + """Import a STIX cyber observable into OpenCTI. + + :param stix_object: Valid STIX2 cyber observable object + :type stix_object: Dict + :param update: Whether to update existing data in the database, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: list, optional + """ # Extract embedded_relationships = self.extract_embedded_relationships(stix_object, types) created_by_id = embedded_relationships["created_by"] @@ -1157,6 +1232,37 @@ def import_observable( reports = embedded_relationships["reports"] sample_refs_ids = embedded_relationships["sample_refs"] + # Extract files + x_opencti_files = [] + if "x_opencti_files" in stix_object: + x_opencti_files.extend(stix_object["x_opencti_files"]) + if self.opencti.get_attribute_in_extension("files", stix_object) is not None: + x_opencti_files.extend( + self.opencti.get_attribute_in_extension("files", stix_object) + ) + + # Prepare all files for direct upload during creation + files_to_upload = [] + files_markings = [] + for file_obj in x_opencti_files: + data = None + if "data" in file_obj: + data = base64.b64decode(file_obj["data"]) + elif "uri" in file_obj: + url = self.opencti.api_url.replace("/graphql", file_obj["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: + files_to_upload.append( + self.opencti.file( + file_obj["name"], + data, + file_obj.get("mime_type", "application/octet-stream"), + ) + ) + files_markings.append(file_obj.get("object_marking_refs", None)) + # Extra extras = { "created_by_id": created_by_id, @@ -1169,7 +1275,12 @@ def import_observable( "external_references_ids": external_references_ids, "reports": reports, "sample_ids": sample_refs_ids, + "files": files_to_upload if files_to_upload else None, + "filesMarkings": files_markings if files_markings else None, } + upsert_operations = self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_object + ) if stix_object["type"] == "simple-observable": stix_observable_result = self.opencti.stix_cyber_observable.create( simple_observable_id=stix_object["id"], @@ -1212,6 +1323,9 @@ def import_observable( extras["granted_refs_ids"] if "granted_refs_ids" in extras else [] ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=upsert_operations, ) else: stix_observable_result = self.opencti.stix_cyber_observable.create( @@ -1236,40 +1350,11 @@ def import_observable( extras["granted_refs_ids"] if "granted_refs_ids" in extras else [] ), update=update, + files=extras.get("files"), + filesMarkings=extras.get("filesMarkings"), + upsert_operations=upsert_operations, ) if stix_observable_result is not None: - # Add files - if "x_opencti_files" in stix_object: - for file in stix_object["x_opencti_files"]: - if "data" in file: - self.opencti.stix_cyber_observable.add_file( - id=stix_observable_result["id"], - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) - if ( - self.opencti.get_attribute_in_extension("files", stix_object) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", stix_object - ): - if "data" in file: - self.opencti.stix_cyber_observable.add_file( - id=stix_observable_result["id"], - file_name=file["name"], - version=file.get("version", None), - data=base64.b64decode(file["data"]), - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) if "id" in stix_object: self.set_in_cache( stix_object["id"], @@ -1330,6 +1415,15 @@ def import_observable( def import_relationship( self, stix_relation: Dict, update: bool = False, types: List = None ) -> None: + """Import a STIX core relationship into OpenCTI. + + :param stix_relation: Valid STIX2 relationship object + :type stix_relation: Dict + :param update: Whether to update existing data in the database, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: list, optional + """ # Extract embedded_relationships = self.extract_embedded_relationships( stix_relation, types @@ -1376,7 +1470,7 @@ def import_relationship( external_reference["source_name"], base_date=datetime.datetime.fromtimestamp(0), ) - except: + except (TypeError, OverflowError): matches = None date = None yesterday = datetime.datetime.now() - datetime.timedelta(days=1) @@ -1389,7 +1483,7 @@ def import_relationship( ): date = match.strftime("%Y-%m-%dT%H:%M:%SZ") break - except: + except (TypeError, OverflowError): date = None stix_relation_result = self.opencti.stix_core_relationship.import_from_stix2( @@ -1430,6 +1524,19 @@ def import_sighting( update: bool = False, types: List = None, ) -> None: + """Import a STIX sighting relationship into OpenCTI. + + :param stix_sighting: Valid STIX2 sighting object + :type stix_sighting: Dict + :param from_id: ID of the source entity (sighting_of_ref) + :type from_id: str + :param to_id: ID of the target entity (where_sighted_ref) + :type to_id: str + :param update: Whether to update existing data in the database, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: list, optional + """ # Extract embedded_relationships = self.extract_embedded_relationships( stix_sighting, types @@ -1460,6 +1567,9 @@ def import_sighting( } # Create the sighting + upsert_operations = self.opencti.get_attribute_in_extension( + "opencti_upsert_operations", stix_sighting + ) if ( "x_opencti_negative" not in stix_sighting @@ -1530,6 +1640,7 @@ def import_sighting( if "x_opencti_ignore_dates" in stix_sighting else None ), + upsert_operations=upsert_operations, ) if stix_sighting_result is not None: self.set_in_cache( @@ -1546,6 +1657,15 @@ def import_sighting( # region export def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> Dict: + """Generate a STIX2 export from an OpenCTI entity. + + :param entity: OpenCTI entity dictionary to export + :type entity: Dict + :param no_custom_attributes: Whether to exclude custom x_opencti attributes, defaults to False + :type no_custom_attributes: bool, optional + :return: STIX2 formatted entity dictionary + :rtype: Dict + """ # Handle model deviation original_entity_type = entity["entity_type"] @@ -1711,10 +1831,7 @@ def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> D ): external_reference["x_opencti_files"] = [] for file in entity_external_reference["importFiles"]: - url = ( - self.opencti.api_url.replace("graphql", "storage/get/") - + file["id"] - ) + url = self.opencti.api_url.replace("/graphql", file["uri"]) data = self.opencti.fetch_opencti_file( url, binary=True, serialize=True ) @@ -1736,8 +1853,8 @@ def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> D if "hashes" in entity: hashes = entity["hashes"] entity["hashes"] = {} - for hash in hashes: - entity["hashes"][hash["algorithm"]] = hash["hash"] + for hash_item in hashes: + entity["hashes"][hash_item["algorithm"]] = hash_item["hash"] # Final entity["x_opencti_id"] = entity["id"] @@ -1757,8 +1874,17 @@ def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> D @staticmethod def prepare_id_filters_export( - id: Union[str, List[str]], access_filter: Dict = None + entity_id: Union[str, List[str]], access_filter: Dict = None ) -> Dict: + """Prepare filter configuration for entity ID-based export queries. + + :param entity_id: Single entity ID or list of entity IDs to filter + :type entity_id: Union[str, List[str]] + :param access_filter: Additional access filter to combine, defaults to None + :type access_filter: Dict, optional + :return: Filter configuration dictionary for API queries + :rtype: Dict + """ if access_filter is not None: return { "mode": "and", @@ -1768,7 +1894,11 @@ def prepare_id_filters_export( "filters": [ { "key": "ids", - "values": id if isinstance(id, list) else [id], + "values": ( + entity_id + if isinstance(entity_id, list) + else [entity_id] + ), } ], "filterGroups": [], @@ -1785,7 +1915,9 @@ def prepare_id_filters_export( { "key": "ids", "mode": "or", - "values": id if isinstance(id, list) else [id], + "values": ( + entity_id if isinstance(entity_id, list) else [entity_id] + ), } ], } @@ -1797,9 +1929,21 @@ def prepare_export( access_filter: Dict = None, no_custom_attributes: bool = False, ) -> List: + """Prepare an entity for STIX2 export with related objects. + + :param entity: Entity dictionary to prepare for export + :type entity: Dict + :param mode: Export mode - 'simple' for entity only, 'full' for entity with relations + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :param no_custom_attributes: Whether to exclude custom attributes, defaults to False + :type no_custom_attributes: bool, optional + :return: List of STIX2 objects ready for export + :rtype: List + """ result = [] objects_to_get = [] - relations_to_get = [] # CreatedByRef if ( @@ -1986,7 +2130,7 @@ def prepare_export( del entity["attribute_count"] from_to_check = entity["from"]["id"] relationships_from_filter = self.prepare_id_filters_export( - id=from_to_check, access_filter=access_filter + entity_id=from_to_check, access_filter=access_filter ) x = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_from_filter @@ -2000,7 +2144,7 @@ def prepare_export( to_to_check = [entity["to"]["id"]] relationships_to_filter = self.prepare_id_filters_export( - id=to_to_check, access_filter=access_filter + entity_id=to_to_check, access_filter=access_filter ) y = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_to_filter @@ -2017,7 +2161,7 @@ def prepare_export( if "from" in entity: from_to_check = entity["from"]["id"] relationships_from_filter = self.prepare_id_filters_export( - id=from_to_check, access_filter=access_filter + entity_id=from_to_check, access_filter=access_filter ) x = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_from_filter @@ -2032,7 +2176,7 @@ def prepare_export( if "to" in entity: to_to_check = [entity["to"]["id"]] relationships_to_filter = self.prepare_id_filters_export( - id=to_to_check, access_filter=access_filter + entity_id=to_to_check, access_filter=access_filter ) y = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_to_filter @@ -2131,8 +2275,8 @@ def prepare_export( # Get extra refs for key in entity.keys(): if key.endswith("_ref"): - type = entity[key].split("--")[0] - if type in STIX_CYBER_OBSERVABLE_MAPPING: + stix_type = entity[key].split("--")[0] + if stix_type in STIX_CYBER_OBSERVABLE_MAPPING: objects_to_get.append( { "id": entity[key], @@ -2150,8 +2294,8 @@ def prepare_export( ) elif key.endswith("_refs"): for value in entity[key]: - type = value.split("--")[0] - if type in STIX_CYBER_OBSERVABLE_MAPPING: + stix_type = value.split("--")[0] + if stix_type in STIX_CYBER_OBSERVABLE_MAPPING: objects_to_get.append( { "id": value, @@ -2239,25 +2383,6 @@ def prepare_export( ) uuids = uuids + [x["id"] for x in entity_object_bundle] result = result + entity_object_bundle - for ( - relation_object - ) in relations_to_get: # never appended after initialization - - def find_relation_object_data(current_relation_object): - return current_relation_object.id == relation_object["id"] - - relation_object_data = self.prepare_export( - entity=filter( - find_relation_object_data, - self.opencti.stix_core_relationship.list(filters=access_filter), - ) - ) - relation_object_bundle = self.filter_objects( - uuids, relation_object_data - ) - uuids = uuids + [x["id"] for x in relation_object_bundle] - result = result + relation_object_bundle - # Get extra reports """ for uuid in uuids: @@ -2294,21 +2419,21 @@ def find_relation_object_data(current_relation_object): # Refilter all the reports object refs final_result = [] - for entity in result: - if entity["type"] in [ + for result_entity in result: + if result_entity["type"] in [ "report", "note", "opinion", "observed-data", "grouping", ]: - if "object_refs" in entity: - entity["object_refs"] = [ - k for k in entity["object_refs"] if k in uuids + if "object_refs" in result_entity: + result_entity["object_refs"] = [ + k for k in result_entity["object_refs"] if k in uuids ] - final_result.append(entity) + final_result.append(result_entity) else: - final_result.append(entity) + final_result.append(result_entity) return final_result else: return [] @@ -2322,6 +2447,23 @@ def get_stix_bundle_or_object_from_entity_id( no_custom_attributes: bool = False, only_entity: bool = False, ) -> Dict: + """Get a STIX2 bundle or single object from an entity ID. + + :param entity_type: Type of the entity to export + :type entity_type: str + :param entity_id: ID of the entity to export + :type entity_id: str + :param mode: Export mode - 'simple' or 'full', defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :param no_custom_attributes: Whether to exclude custom attributes, defaults to False + :type no_custom_attributes: bool, optional + :param only_entity: If True, return only the entity object instead of a bundle + :type only_entity: bool, optional + :return: STIX2 bundle dictionary or single STIX2 object if only_entity is True + :rtype: Dict + """ bundle = { "type": "bundle", "id": "bundle--" + str(uuid.uuid4()), @@ -2350,7 +2492,7 @@ def get_stix_bundle_or_object_from_entity_id( return bundle # Please use get_stix_bundle_or_object_from_entity_id instead - @DeprecationWarning + @deprecated("Use get_stix_bundle_or_object_from_entity_id instead") def export_entity( self, entity_type: str, @@ -2360,6 +2502,26 @@ def export_entity( no_custom_attributes: bool = False, only_entity: bool = False, ) -> Dict: + """Export an entity as a STIX2 bundle. + + .. deprecated:: + Use :meth:`get_stix_bundle_or_object_from_entity_id` instead. + + :param entity_type: Type of the entity to export + :type entity_type: str + :param entity_id: ID of the entity to export + :type entity_id: str + :param mode: Export mode - 'simple' or 'full', defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :param no_custom_attributes: Whether to exclude custom attributes, defaults to False + :type no_custom_attributes: bool, optional + :param only_entity: If True, return only the entity object instead of a bundle + :type only_entity: bool, optional + :return: STIX2 bundle dictionary or single STIX2 object + :rtype: Dict + """ return self.get_stix_bundle_or_object_from_entity_id( entity_type=entity_type, entity_id=entity_id, @@ -2378,7 +2540,26 @@ def export_entities_list( orderMode: str = None, getAll: bool = True, withFiles: bool = False, - ) -> [Dict]: + ) -> List[Dict]: + """List entities for export based on type and filters. + + :param entity_type: Type of entities to list + :type entity_type: str + :param search: Search parameters, defaults to None + :type search: Dict, optional + :param filters: Filter parameters, defaults to None + :type filters: Dict, optional + :param orderBy: Field to order results by, defaults to None + :type orderBy: str, optional + :param orderMode: Order direction ('asc' or 'desc'), defaults to None + :type orderMode: str, optional + :param getAll: Whether to get all results, defaults to True + :type getAll: bool, optional + :param withFiles: Whether to include files in the export, defaults to False + :type withFiles: bool, optional + :return: List of entity dictionaries + :rtype: List[Dict] + """ if IdentityTypes.has_value(entity_type): entity_type = "Identity" @@ -2460,6 +2641,25 @@ def export_list( mode: str = "simple", access_filter: Dict = None, ) -> Dict: + """Export a list of entities as a STIX2 bundle. + + :param entity_type: Type of entities to export + :type entity_type: str + :param search: Search parameters, defaults to None + :type search: Dict, optional + :param filters: Filter parameters, defaults to None + :type filters: Dict, optional + :param order_by: Field to order results by, defaults to None + :type order_by: str, optional + :param order_mode: Order direction ('asc' or 'desc'), defaults to None + :type order_mode: str, optional + :param mode: Export mode - 'simple' or 'full', defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :return: STIX2 bundle containing all exported entities + :rtype: Dict + """ bundle = { "type": "bundle", "id": "bundle--" + str(uuid.uuid4()), @@ -2501,11 +2701,21 @@ def export_list( def export_selected( self, - entities_list: [dict], + entities_list: List[dict], mode: str = "simple", access_filter: Dict = None, ) -> Dict: - + """Export selected entities as a STIX2 bundle. + + :param entities_list: List of entities to export + :type entities_list: List[dict] + :param mode: Export mode ('simple' or 'full'), defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export + :type access_filter: Dict + :return: STIX2 bundle containing exported entities + :rtype: Dict + """ bundle = { "type": "bundle", "id": "bundle--" + str(uuid.uuid4()), @@ -2530,6 +2740,11 @@ def export_selected( return bundle def apply_patch_files(self, item): + """Apply file patches to an item. + + :param item: Item containing file patch operations + :type item: dict + """ field_patch = self.opencti.get_attribute_in_extension( "opencti_field_patch", item ) @@ -2561,6 +2776,11 @@ def apply_patch_files(self, item): ) def apply_patch(self, item): + """Apply field patches to an item. + + :param item: Item containing field patch operations + :type item: dict + """ field_patch = self.opencti.get_attribute_in_extension( "opencti_field_patch", item ) @@ -2608,21 +2828,41 @@ def apply_patch(self, item): self.apply_patch_files(item) def rule_apply(self, item): + """Apply a rule to an item. + + :param item: Item to apply the rule to + :type item: dict + """ rule_id = self.opencti.get_attribute_in_extension("opencti_rule", item) if rule_id is None: rule_id = item["opencti_rule"] self.opencti.stix_core_object.rule_apply(element_id=item["id"], rule_id=rule_id) def rule_clear(self, item): + """Clear a rule from an item. + + :param item: Item to clear the rule from + :type item: dict + """ rule_id = self.opencti.get_attribute_in_extension("opencti_rule", item) if rule_id is None: rule_id = item["opencti_rule"] self.opencti.stix_core_object.rule_clear(element_id=item["id"], rule_id=rule_id) def rules_rescan(self, item): + """Rescan rules for an item. + + :param item: Item to rescan rules for + :type item: dict + """ self.opencti.stix_core_object.rules_rescan(element_id=item["id"]) def organization_share(self, item): + """Share an item with organizations. + + :param item: Item to share + :type item: dict + """ organization_ids = self.opencti.get_attribute_in_extension( "sharing_organization_ids", item ) @@ -2649,6 +2889,11 @@ def organization_share(self, item): ) def organization_unshare(self, item): + """Unshare an item from organizations. + + :param item: Item to unshare + :type item: dict + """ organization_ids = self.opencti.get_attribute_in_extension( "sharing_organization_ids", item ) @@ -2674,6 +2919,12 @@ def organization_unshare(self, item): ) def element_add_organizations(self, item): + """Add organizations to an element. + + :param item: Item to add organizations to + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ organization_ids = self.opencti.get_attribute_in_extension( "organization_ids", item ) @@ -2691,6 +2942,12 @@ def element_add_organizations(self, item): ) def element_remove_organizations(self, item): + """Remove organizations from an element. + + :param item: Item to remove organizations from + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ organization_ids = self.opencti.get_attribute_in_extension( "organization_ids", item ) @@ -2708,6 +2965,12 @@ def element_remove_organizations(self, item): ) def element_add_groups(self, item): + """Add groups to an element. + + :param item: Item to add groups to + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ group_ids = self.opencti.get_attribute_in_extension("group_ids", item) if group_ids is None: group_ids = item["group_ids"] @@ -2720,6 +2983,12 @@ def element_add_groups(self, item): ) def element_remove_groups(self, item): + """Remove groups from an element. + + :param item: Item to remove groups from + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ group_ids = self.opencti.get_attribute_in_extension("group_ids", item) if group_ids is None: group_ids = item["group_ids"] @@ -2733,6 +3002,12 @@ def element_remove_groups(self, item): ) def send_email(self, item): + """Send an email for an item. + + :param item: Item to send email for + :type item: dict + :raises ValueError: If the operation is not supported for the item type + """ template_id = self.opencti.get_attribute_in_extension("template_id", item) if template_id is None: template_id = item["template_id"] @@ -2745,6 +3020,14 @@ def send_email(self, item): ) def element_operation_delete(self, item, operation): + """Delete an element. + + :param item: Item to delete + :type item: dict + :param operation: Delete operation type ('delete' or 'delete_force') + :type operation: str + :raises ValueError: If the delete operation fails or helper not found + """ # If data is stix, just use the generic stix function for deletion force_delete = operation == "delete_force" if item["type"] == "relationship": @@ -2770,6 +3053,11 @@ def element_operation_delete(self, item, operation): ) def element_remove_from_draft(self, item): + """Remove an element from draft. + + :param item: Item to remove from draft + :type item: dict + """ if item["type"] == "relationship": self.opencti.stix_core_relationship.remove_from_draft(id=item["id"]) elif item["type"] == "sighting": @@ -2779,6 +3067,14 @@ def element_remove_from_draft(self, item): self.opencti.stix_core_object.remove_from_draft(id=item["id"]) def apply_opencti_operation(self, item, operation): + """Apply an OpenCTI operation to an item. + + :param item: Item to apply the operation to + :type item: dict + :param operation: Operation to apply (delete, restore, merge, patch, etc.) + :type operation: str + :raises ValueError: If the operation is not supported + """ if operation == "delete" or operation == "delete_force": self.element_operation_delete(item=item, operation=operation) elif operation == "revert_draft": @@ -2798,13 +3094,13 @@ def apply_opencti_operation(self, item, operation): elif operation == "patch": self.apply_patch(item=item) elif operation == "pir_flag_element": - id = item["id"] - input = item["input"] - self.opencti.pir.pir_flag_element(id=id, input=input) + element_id = item["id"] + pir_input = item["input"] + self.opencti.pir.pir_flag_element(id=element_id, input=pir_input) elif operation == "pir_unflag_element": - id = item["id"] - input = item["input"] - self.opencti.pir.pir_unflag_element(id=id, input=input) + element_id = item["id"] + pir_input = item["input"] + self.opencti.pir.pir_unflag_element(id=element_id, input=pir_input) elif operation == "rule_apply": self.rule_apply(item=item) elif operation == "rule_clear": @@ -2849,242 +3145,262 @@ def import_item( item, update: bool = False, types: List = None, - processing_count: int = 0, work_id: str = None, ): - worker_logger = self.opencti.logger_class("worker") - # Ultimate protection to avoid infinite retry - if processing_count > MAX_PROCESSING_COUNT: - if work_id is not None: - item_str = json.dumps(item) - self.opencti.work.report_expectation( - work_id, - { - "error": "Max number of retries reached, please see error logs of workers for more details", - "source": ( - item_str if len(item_str) < 50000 else "Bundle too large" - ), - }, - ) - return False - try: - self.opencti.set_retry_number(processing_count) - opencti_operation = self.opencti.get_attribute_in_extension( - "opencti_operation", item - ) - if opencti_operation is not None: - self.apply_opencti_operation(item, opencti_operation) - elif "opencti_operation" in item: - self.apply_opencti_operation(item, item["opencti_operation"]) - elif item["type"] == "relationship": - # Import relationship - self.import_relationship(item, update, types) - elif item["type"] == "sighting": - # region Resolve the to - to_ids = [] - if "x_opencti_where_sighted_refs" in item: - for where_sighted_ref in item["x_opencti_where_sighted_refs"]: - to_ids.append(where_sighted_ref) - elif "where_sighted_refs" in item: - for where_sighted_ref in item["where_sighted_refs"]: - to_ids.append(where_sighted_ref) - # endregion - # region Resolve the from - from_id = None - if "x_opencti_sighting_of_ref" in item: - from_id = item["x_opencti_sighting_of_ref"] - elif "sighting_of_ref" in item: - from_id = item["sighting_of_ref"] - # endregion - # region create the sightings - if len(to_ids) > 0: - if from_id: + """Import a single STIX2 item into OpenCTI. + + :param item: STIX2 item to import + :type item: dict + :param update: Whether to update existing data, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: List, optional + :param work_id: Work ID for tracking import progress, defaults to None + :type work_id: str, optional + :return: True on success + :rtype: bool + """ + opencti_operation = self.opencti.get_attribute_in_extension( + "opencti_operation", item + ) + if opencti_operation is not None: + self.apply_opencti_operation(item, opencti_operation) + elif "opencti_operation" in item: + self.apply_opencti_operation(item, item["opencti_operation"]) + elif item["type"] == "relationship": + # Import relationship + self.import_relationship(item, update, types) + elif item["type"] == "sighting": + # region Resolve the to + to_ids = [] + if "x_opencti_where_sighted_refs" in item: + for where_sighted_ref in item["x_opencti_where_sighted_refs"]: + to_ids.append(where_sighted_ref) + elif "where_sighted_refs" in item: + for where_sighted_ref in item["where_sighted_refs"]: + to_ids.append(where_sighted_ref) + # endregion + # region Resolve the from + from_id = None + if "x_opencti_sighting_of_ref" in item: + from_id = item["x_opencti_sighting_of_ref"] + elif "sighting_of_ref" in item: + from_id = item["sighting_of_ref"] + # endregion + # region create the sightings + if len(to_ids) > 0: + if from_id: + for to_id in to_ids: + self.import_sighting(item, from_id, to_id, update) + # Import observed_data_refs + if "observed_data_refs" in item: + for observed_data_ref in item["observed_data_refs"]: for to_id in to_ids: - self.import_sighting(item, from_id, to_id, update) - # Import observed_data_refs - if "observed_data_refs" in item: - for observed_data_ref in item["observed_data_refs"]: - for to_id in to_ids: - self.import_sighting( - item, observed_data_ref, to_id, update - ) - # endregion - elif item["type"] == "label": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.label.create( - stix_id=item["id"], - value=item["value"], - color=item["color"], - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif item["type"] == "vocabulary": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.vocabulary.create( - stix_id=item["id"], - name=item["name"], - category=item["category"], - description=( - item["description"] if "description" in item else None - ), - aliases=item["aliases"] if "aliases" in item else None, - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif item["type"] == "external-reference": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.external_reference.create( - stix_id=item["id"], - source_name=( - item["source_name"] if "source_name" in item else None - ), - url=item["url"] if "url" in item else None, - external_id=( - item["external_id"] if "external_id" in item else None - ), - description=( - item["description"] if "description" in item else None - ), - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif item["type"] == "kill-chain-phase": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.kill_chain_phase.create( - stix_id=item["id"], - kill_chain_name=item["kill_chain_name"], - phase_name=item["phase_name"], - x_opencti_order=item["order"] if "order" in item else 0, - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif StixCyberObservableTypes.has_value(item["type"]): - if types is None or len(types) == 0: - self.import_observable(item, update, types) - elif item["type"] in types or "observable" in types: - self.import_observable(item, update, types) + self.import_sighting(item, observed_data_ref, to_id, update) + # endregion + elif item["type"] == "label": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.label.create( + stix_id=item["id"], + value=item["value"], + color=item["color"], + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif item["type"] == "vocabulary": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.vocabulary.create( + stix_id=item["id"], + name=item["name"], + category=item["category"], + description=(item["description"] if "description" in item else None), + aliases=item["aliases"] if "aliases" in item else None, + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif item["type"] == "external-reference": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.external_reference.create( + stix_id=item["id"], + source_name=(item["source_name"] if "source_name" in item else None), + url=item["url"] if "url" in item else None, + external_id=(item["external_id"] if "external_id" in item else None), + description=(item["description"] if "description" in item else None), + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif item["type"] == "kill-chain-phase": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.kill_chain_phase.create( + stix_id=item["id"], + kill_chain_name=item["kill_chain_name"], + phase_name=item["phase_name"], + x_opencti_order=item["order"] if "order" in item else 0, + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif StixCyberObservableTypes.has_value(item["type"]): + if types is None or len(types) == 0: + self.import_observable(item, update, types) + elif item["type"] in types or "observable" in types: + self.import_observable(item, update, types) + else: + # Check the scope + if item["type"] == "marking-definition" or types is None or len(types) == 0: + self.import_object(item, update, types) + # Handle identity & location if part of the scope + elif item["type"] in types: + self.import_object(item, update, types) else: - # Check the scope - if ( - item["type"] == "marking-definition" - or types is None - or len(types) == 0 - ): - self.import_object(item, update, types) - # Handle identity & location if part of the scope - elif item["type"] in types: - self.import_object(item, update, types) - else: - # Specific OpenCTI scopes - if item["type"] == "identity": - if "identity_class" in item: - if ("class" in types or "sector" in types) and item[ - "identity_class" - ] == "class": - self.import_object(item, update, types) - elif item["identity_class"] in types: - self.import_object(item, update, types) - elif item["type"] == "location": - if "x_opencti_location_type" in item: - if item["x_opencti_location_type"].lower() in types: - self.import_object(item, update, types) - elif ( + # Specific OpenCTI scopes + if item["type"] == "identity": + if "identity_class" in item: + if ("class" in types or "sector" in types) and item[ + "identity_class" + ] == "class": + self.import_object(item, update, types) + elif item["identity_class"] in types: + self.import_object(item, update, types) + elif item["type"] == "location": + if "x_opencti_location_type" in item: + if item["x_opencti_location_type"].lower() in types: + self.import_object(item, update, types) + elif ( + self.opencti.get_attribute_in_extension("location_type", item) + is not None + ): + if ( self.opencti.get_attribute_in_extension( "location_type", item - ) - is not None + ).lower() + in types ): - if ( - self.opencti.get_attribute_in_extension( - "location_type", item - ).lower() - in types - ): - self.import_object(item, update, types) - if work_id is not None: - self.opencti.work.report_expectation(work_id, None) - bundles_success_counter.add(1) - return True - except (RequestException, Timeout): - bundles_timeout_error_counter.add(1) - worker_logger.warning("A connection error or timeout occurred") - # Platform is under heavy load: wait for unlock & retry almost indefinitely. - sleep_jitter = round(random.uniform(10, 30), 2) - time.sleep(sleep_jitter) - return self.import_item(item, update, types, processing_count + 1, work_id) - except Exception as ex: # pylint: disable=broad-except - error = str(ex) - error_msg = traceback.format_exc() - in_retry = processing_count < PROCESSING_COUNT - # Platform is under heavy load, wait for unlock & retry indefinitely. - if ERROR_TYPE_LOCK in error_msg: - bundles_lock_error_counter.add(1) - sleep_jitter = round(random.uniform(1, 3), 2) - time.sleep(sleep_jitter) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # Platform detects a missing reference and have to retry - elif ERROR_TYPE_MISSING_REFERENCE in error_msg and in_retry: - bundles_missing_reference_error_counter.add(1) - sleep_jitter = round(random.uniform(1, 3), 2) + self.import_object(item, update, types) + if work_id is not None: + self.opencti.work.report_expectation(work_id, None) + bundles_success_counter.add(1) + return True + + def import_item_with_retries( + self, + item, + update: bool = False, + types: List = None, + work_id: str = None, + ): + """Import a single STIX2 item with automatic retry on failures. + + Handles various error types including timeouts, lock errors, missing references, + and bad gateway errors with appropriate retry strategies. + + :param item: STIX2 item to import + :type item: dict + :param update: Whether to update existing data, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: List, optional + :param work_id: Work ID for tracking import progress, defaults to None + :type work_id: str, optional + :return: None on success, the failed item on permanent failure + :rtype: dict or None + """ + processing_count = 0 + worker_logger = self.opencti.logger_class("worker") + while processing_count <= MAX_PROCESSING_COUNT: + try: + self.opencti.set_retry_number(processing_count) + self.import_item(item, update, types, work_id) + return None + except (RequestException, Timeout): + bundles_timeout_error_counter.add(1) + worker_logger.warning("A connection error or timeout occurred") + # Platform is under heavy load: wait for unlock & retry almost indefinitely. + sleep_jitter = round(random.uniform(10, 30), 2) time.sleep(sleep_jitter) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # A bad gateway error occurs - elif ERROR_TYPE_BAD_GATEWAY in error_msg: - worker_logger.error( - "Message reprocess for bad gateway", - {"count": processing_count}, - ) - bundles_bad_gateway_error_counter.add(1) - time.sleep(60) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # Request timeout error occurs - elif ERROR_TYPE_TIMEOUT in error_msg: - worker_logger.error( - "Message reprocess for request timed out", - {"count": processing_count}, - ) - bundles_timed_out_error_counter.add(1) - time.sleep(60) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # A draft lock error occurs - elif ERROR_TYPE_DRAFT_LOCK in error_msg: - bundles_technical_error_counter.add(1) - if work_id is not None: - self.opencti.work.api.set_draft_id("") - self.opencti.work.report_expectation( - work_id, - { - "error": error, - "source": "Draft in read only", - }, + processing_count += 1 + except Exception as ex: # pylint: disable=broad-except + error = str(ex) + error_msg = traceback.format_exc() + in_retry = processing_count < PROCESSING_COUNT + # Platform is under heavy load, wait for unlock & retry indefinitely. + if ERROR_TYPE_LOCK in error_msg: + bundles_lock_error_counter.add(1) + sleep_jitter = round(random.uniform(1, 3), 2) + time.sleep(sleep_jitter) + processing_count += 1 + # Platform detects a missing reference and have to retry + elif ERROR_TYPE_MISSING_REFERENCE in error_msg and in_retry: + bundles_missing_reference_error_counter.add(1) + sleep_jitter = round(random.uniform(1, 3), 2) + time.sleep(sleep_jitter) + processing_count += 1 + # A bad gateway error occurs + elif ERROR_TYPE_BAD_GATEWAY in error_msg: + worker_logger.error( + "Message reprocess for bad gateway", + {"count": processing_count}, ) - return False - # Platform does not know what to do and raises an error: - # That also works for missing reference with too much execution - else: - bundles_technical_error_counter.add(1) - if work_id is not None: - item_str = json.dumps(item) - self.opencti.work.report_expectation( - work_id, - { - "error": error, - "source": ( - item_str - if len(item_str) < 50000 - else "Bundle too large" - ), - }, + bundles_bad_gateway_error_counter.add(1) + time.sleep(60) + processing_count += 1 + # Request timeout error occurs + elif ERROR_TYPE_TIMEOUT in error_msg: + worker_logger.error( + "Message reprocess for request timed out", + {"count": processing_count}, + ) + bundles_timed_out_error_counter.add(1) + time.sleep(60) + processing_count += 1 + # A draft lock error occurs + elif ERROR_TYPE_DRAFT_LOCK in error_msg: + bundles_technical_error_counter.add(1) + if work_id is not None: + self.opencti.work.api.set_draft_id("") + self.opencti.work.report_expectation( + work_id, + { + "error": error, + "source": "Draft in read only", + }, + ) + return None + # Platform does not know what to do and raises an error: + # That also works for missing reference with too much execution + else: + bundles_technical_error_counter.add(1) + worker_logger.error( + "Unrecognized error during bundle import", {"error": error} ) - return False + if work_id is not None: + item_str = json.dumps(item) + self.opencti.work.report_expectation( + work_id, + { + "error": error, + "source": ( + item_str + if len(item_str) < 50000 + else "Bundle too large" + ), + }, + ) + return None + + max_retry_error_message = "Max number of retries reached, please see error logs of workers for more details. Bundle will be sent to dead letter queue." + worker_logger.error(max_retry_error_message) + if work_id is not None: + item_str = json.dumps(item) + self.opencti.work.report_expectation( + work_id, + { + "error": max_retry_error_message, + "source": ( + item_str if len(item_str) < 50000 else "Bundle too large" + ), + }, + ) + return item def import_bundle( self, @@ -3094,6 +3410,23 @@ def import_bundle( work_id: str = None, objects_max_refs: int = 0, ) -> Tuple[list, list]: + """Import a complete STIX2 bundle into OpenCTI. + + :param stix_bundle: STIX2 bundle dictionary to import + :type stix_bundle: Dict + :param update: Whether to update existing data, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: List, optional + :param work_id: Work ID for tracking import progress, defaults to None + :type work_id: str, optional + :param objects_max_refs: Maximum number of object references allowed; objects exceeding + this limit will be rejected. Set to 0 to disable the limit. + :type objects_max_refs: int, optional + :return: Tuple of (list of successfully imported elements, list of failed/too-large elements) + :rtype: Tuple[list, list] + :raises ValueError: If the bundle is not properly formatted or empty + """ # Check if the bundle is correctly formatted if "type" not in stix_bundle or stix_bundle["type"] != "bundle": raise ValueError("JSON data type is not a STIX2 bundle") @@ -3144,31 +3477,53 @@ def import_bundle( ) too_large_elements_bundles.append(item) else: - self.import_item(item, update, types, 0, work_id) - imported_elements.append({"id": item["id"], "type": item["type"]}) + failed_item = self.import_item_with_retries( + item, update, types, work_id + ) + if failed_item is not None: + too_large_elements_bundles.append(item) + else: + imported_elements.append( + {"id": item["id"], "type": item["type"]} + ) return imported_elements, too_large_elements_bundles @staticmethod def put_attribute_in_extension( - object, extension_id, key, value, multiple=False + stix_object, extension_id, key, value, multiple=False ) -> any: - if ("x_opencti_" + key) in object: - del object["x_opencti_" + key] - if ("x_mitre_" + key) in object: - del object["x_mitre_" + key] - if "extensions" not in object: - object["extensions"] = {} - if extension_id not in object["extensions"]: - object["extensions"][extension_id] = {} - if key in object["extensions"][extension_id]: + """Add or update an attribute in a STIX object's extension. + + :param stix_object: STIX object to modify + :type stix_object: dict + :param extension_id: ID of the extension to add the attribute to + :type extension_id: str + :param key: Attribute key name + :type key: str + :param value: Attribute value to set + :type value: any + :param multiple: If True, append value to a list; if False, replace the value + :type multiple: bool + :return: Modified STIX object + :rtype: dict + """ + if ("x_opencti_" + key) in stix_object: + del stix_object["x_opencti_" + key] + if ("x_mitre_" + key) in stix_object: + del stix_object["x_mitre_" + key] + if "extensions" not in stix_object: + stix_object["extensions"] = {} + if extension_id not in stix_object["extensions"]: + stix_object["extensions"][extension_id] = {} + if key in stix_object["extensions"][extension_id]: if multiple: - object["extensions"][extension_id][key].append(value) + stix_object["extensions"][extension_id][key].append(value) else: - object["extensions"][extension_id][key] = value + stix_object["extensions"][extension_id][key] = value else: if multiple: - object["extensions"][extension_id][key] = [value] + stix_object["extensions"][extension_id][key] = [value] else: - object["extensions"][extension_id][key] = value - return object + stix_object["extensions"][extension_id][key] = value + return stix_object diff --git a/client-python/pycti/utils/opencti_stix2_identifier.py b/client-python/pycti/utils/opencti_stix2_identifier.py index 8755e1695919..619c885d5f3b 100644 --- a/client-python/pycti/utils/opencti_stix2_identifier.py +++ b/client-python/pycti/utils/opencti_stix2_identifier.py @@ -4,6 +4,17 @@ def external_reference_generate_id(url=None, source_name=None, external_id=None): + """Generate a STIX ID for an external reference. + + :param url: URL of the external reference + :type url: str + :param source_name: Source name of the external reference + :type source_name: str + :param external_id: External ID of the reference + :type external_id: str + :return: Generated STIX ID or None if insufficient data + :rtype: str or None + """ if url is not None: data = {"url": url} elif source_name is not None and external_id is not None: @@ -11,12 +22,25 @@ def external_reference_generate_id(url=None, source_name=None, external_id=None) else: return None data = canonicalize(data, utf8=False) - id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) - return "external-reference--" + id + generated_id = str( + uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data) + ) + return "external-reference--" + generated_id def kill_chain_phase_generate_id(phase_name, kill_chain_name): + """Generate a STIX ID for a kill chain phase. + + :param phase_name: Name of the phase + :type phase_name: str + :param kill_chain_name: Name of the kill chain + :type kill_chain_name: str + :return: Generated STIX ID + :rtype: str + """ data = {"phase_name": phase_name, "kill_chain_name": kill_chain_name} data = canonicalize(data, utf8=False) - id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) - return "kill-chain-phase--" + id + generated_id = str( + uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data) + ) + return "kill-chain-phase--" + generated_id diff --git a/client-python/pycti/utils/opencti_stix2_splitter.py b/client-python/pycti/utils/opencti_stix2_splitter.py index 75f6c9e3be6d..2c89618df139 100644 --- a/client-python/pycti/utils/opencti_stix2_splitter.py +++ b/client-python/pycti/utils/opencti_stix2_splitter.py @@ -26,6 +26,13 @@ def is_id_supported(key): + """Check if a STIX ID type is supported for processing. + + :param key: STIX ID or identifier to check + :type key: str + :return: True if the ID type is supported, False otherwise + :rtype: bool + """ if "--" in key: id_type = key.split("--")[0] return id_type in supported_types @@ -34,18 +41,31 @@ def is_id_supported(key): class OpenCTIStix2Splitter: - """STIX2 bundle splitter for OpenCTI + """STIX2 bundle splitter for OpenCTI. - Splits large STIX2 bundles into smaller chunks for processing. + Splits large STIX2 bundles into smaller chunks for processing, + handling dependencies between objects and deduplicating references. """ def __init__(self): + """Initialize the STIX2 bundle splitter. + + Sets up internal caches for tracking processed elements, + references, and incompatible items. + """ self.cache_index = {} self.cache_refs = {} self.elements = [] self.incompatible_items = [] def get_internal_ids_in_extension(self, item): + """Get internal IDs from OpenCTI extensions in a STIX object. + + :param item: the STIX object to extract IDs from + :type item: dict + :return: list of internal IDs found in extensions + :rtype: list + """ ids = [] if item.get("x_opencti_id"): ids.append(item["x_opencti_id"]) @@ -60,6 +80,19 @@ def get_internal_ids_in_extension(self, item): def enlist_element( self, item_id, raw_data, cleanup_inconsistent_bundle, parent_acc ): + """Enlist an element and its dependencies for processing. + + :param item_id: the ID of the item to enlist + :type item_id: str + :param raw_data: the raw data dictionary of all items + :type raw_data: dict + :param cleanup_inconsistent_bundle: whether to cleanup inconsistent references + :type cleanup_inconsistent_bundle: bool + :param parent_acc: accumulator of parent IDs to prevent circular references + :type parent_acc: list + :return: number of dependencies enlisted + :rtype: int + """ nb_deps = 1 if item_id not in raw_data: return 0 @@ -191,8 +224,8 @@ def enlist_element( ) elif item["type"] == "sighting": is_compatible = ( - item["sighting_of_ref"] is not None - and len(item["where_sighted_refs"]) > 0 + item.get("sighting_of_ref") is not None + and len(item.get("where_sighted_refs", [])) > 0 ) else: is_compatible = is_id_supported(item_id) @@ -214,12 +247,24 @@ def split_bundle_with_expectations( event_version=None, cleanup_inconsistent_bundle=False, ) -> Tuple[int, list, list]: - """splits a valid stix2 bundle into a list of bundles""" + """Split a valid STIX2 bundle into a list of bundles. + + :param bundle: the STIX2 bundle to split + :type bundle: str or dict + :param use_json: whether the bundle is JSON string (True) or dict (False) + :type use_json: bool + :param event_version: (optional) event version to include in bundles + :type event_version: str or None + :param cleanup_inconsistent_bundle: whether to cleanup inconsistent references + :type cleanup_inconsistent_bundle: bool + :return: tuple of (number of expectations, incompatible items, list of bundles) + :rtype: Tuple[int, list, list] + """ if use_json: try: bundle_data = json.loads(bundle) - except: - raise Exception("File data is not a valid JSON") + except json.JSONDecodeError as e: + raise Exception(f"File data is not a valid JSON: {e}") else: bundle_data = bundle @@ -242,6 +287,13 @@ def split_bundle_with_expectations( bundles = [] def by_dep_size(elem): + """Get the dependency count for sorting elements. + + :param elem: Element dictionary containing nb_deps + :type elem: dict + :return: Number of dependencies + :rtype: int + """ return elem["nb_deps"] self.elements.sort(key=by_dep_size) @@ -271,6 +323,20 @@ def by_dep_size(elem): @deprecated("Use split_bundle_with_expectations instead") def split_bundle(self, bundle, use_json=True, event_version=None) -> list: + """Split a valid STIX2 bundle into a list of bundles. + + .. deprecated:: + Use :meth:`split_bundle_with_expectations` instead. + + :param bundle: the STIX2 bundle to split + :type bundle: str or dict + :param use_json: whether the bundle is JSON string (True) or dict (False) + :type use_json: bool + :param event_version: (optional) event version to include in bundles + :type event_version: str or None + :return: list of STIX2 bundles + :rtype: list + """ _, _, bundles = self.split_bundle_with_expectations( bundle, use_json, event_version ) @@ -278,14 +344,20 @@ def split_bundle(self, bundle, use_json=True, event_version=None) -> list: @staticmethod def stix2_create_bundle(bundle_id, bundle_seq, items, use_json, event_version=None): - """create a stix2 bundle with items - - :param items: valid stix2 items - :type items: - :param use_json: use JSON? - :type use_json: - :return: JSON of the stix2 bundle - :rtype: + """Create a STIX2 bundle with items. + + :param bundle_id: the bundle ID + :type bundle_id: str + :param bundle_seq: the bundle sequence number + :type bundle_seq: int + :param items: valid STIX2 items + :type items: list + :param use_json: whether to return JSON string (True) or dict (False) + :type use_json: bool + :param event_version: (optional) event version to include + :type event_version: str or None + :return: STIX2 bundle as JSON string or dict + :rtype: str or dict """ bundle = { diff --git a/client-python/pycti/utils/opencti_stix2_update.py b/client-python/pycti/utils/opencti_stix2_update.py index 5c8b0f9e55d4..fc958ea52e36 100644 --- a/client-python/pycti/utils/opencti_stix2_update.py +++ b/client-python/pycti/utils/opencti_stix2_update.py @@ -1,63 +1,107 @@ -# coding: utf-8 - from pycti.utils.constants import StixCyberObservableTypes class OpenCTIStix2Update: - """Python API for Stix2 Update in OpenCTI + """Python API for Stix2 Update in OpenCTI. + + Provides methods to update STIX2 objects in OpenCTI, including + adding/removing marking definitions, labels, external references, + kill chain phases, and object references. - :param opencti: OpenCTI instance + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the OpenCTIStix2Update helper. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.mapping_cache = {} - def add_object_marking_refs(self, entity_type, id, object_marking_refs, version=2): + def add_object_marking_refs( + self, entity_type, entity_id, object_marking_refs, version=2 + ): + """Add marking definition references to an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_marking_ref in object_marking_refs: if version == 2: object_marking_ref = object_marking_ref["value"] if entity_type == "relationship": self.opencti.stix_core_relationship.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif entity_type == "sighting": self.opencti.stix_sighting_relationship.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) else: self.opencti.stix_domain_object.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) def remove_object_marking_refs( - self, entity_type, id, object_marking_refs, version=2 + self, entity_type, entity_id, object_marking_refs, version=2 ): + """Remove marking definition references from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_marking_ref in object_marking_refs: if version == 2: object_marking_ref = object_marking_ref["value"] if entity_type == "relationship": self.opencti.stix_core_relationship.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif entity_type == "sighting": self.opencti.stix_sighting_relationship.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) else: self.opencti.stix_domain_object.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) - def add_external_references(self, entity_type, id, external_references, version=2): + def add_external_references( + self, entity_type, entity_id, external_references, version=2 + ): + """Add external references to an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param external_references: List of external references + :type external_references: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for external_reference in external_references: if version == 2: external_reference = external_reference["value"] @@ -82,33 +126,55 @@ def add_external_references(self, entity_type, id, external_references, version= )["id"] if entity_type == "relationship": self.opencti.stix_core_relationship.add_external_reference( - id=id, external_reference_id=external_reference_id + id=entity_id, external_reference_id=external_reference_id ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.add_external_reference( - id=id, external_reference_id=external_reference_id + id=entity_id, external_reference_id=external_reference_id ) else: self.opencti.stix_domain_object.add_external_reference( - id=id, external_reference_id=external_reference_id + id=entity_id, external_reference_id=external_reference_id ) - def remove_external_references(self, entity_type, id, external_references): + def remove_external_references(self, entity_type, entity_id, external_references): + """Remove external references from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param external_references: List of external references + :type external_references: list + """ for external_reference in external_references: if entity_type == "relationship": self.opencti.stix_core_relationship.remove_external_reference( - id=id, external_reference_id=external_reference["id"] + id=entity_id, external_reference_id=external_reference["id"] ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.remove_external_reference( - id=id, external_reference_id=external_reference["id"] + id=entity_id, external_reference_id=external_reference["id"] ) else: self.opencti.stix_domain_object.remove_external_reference( - id=id, external_reference_id=external_reference["id"] + id=entity_id, external_reference_id=external_reference["id"] ) - def add_kill_chain_phases(self, entity_type, id, kill_chain_phases, version=2): + def add_kill_chain_phases( + self, entity_type, entity_id, kill_chain_phases, version=2 + ): + """Add kill chain phases to an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param kill_chain_phases: List of kill chain phases + :type kill_chain_phases: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for kill_chain_phase in kill_chain_phases: if version == 2: kill_chain_phase = kill_chain_phase["value"] @@ -124,147 +190,295 @@ def add_kill_chain_phases(self, entity_type, id, kill_chain_phases, version=2): )["id"] if entity_type == "relationship": self.opencti.stix_core_relationship.add_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase_id + id=entity_id, kill_chain_phase_id=kill_chain_phase_id ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.add_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase_id + id=entity_id, kill_chain_phase_id=kill_chain_phase_id ) else: self.opencti.stix_domain_object.add_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase_id + id=entity_id, kill_chain_phase_id=kill_chain_phase_id ) - def remove_kill_chain_phases(self, entity_type, id, kill_chain_phases): + def remove_kill_chain_phases(self, entity_type, entity_id, kill_chain_phases): + """Remove kill chain phases from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param kill_chain_phases: List of kill chain phases + :type kill_chain_phases: list + """ for kill_chain_phase in kill_chain_phases: if entity_type == "relationship": self.opencti.stix_core_relationship.remove_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase["id"] + id=entity_id, kill_chain_phase_id=kill_chain_phase["id"] ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.remove_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase["id"] + id=entity_id, kill_chain_phase_id=kill_chain_phase["id"] ) else: self.opencti.stix_domain_object.remove_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase["id"] + id=entity_id, kill_chain_phase_id=kill_chain_phase["id"] ) - def add_object_refs(self, entity_type, id, object_refs, version=2): + def add_object_refs(self, entity_type, entity_id, object_refs, version=2): + """Add object references to a container entity. + + :param entity_type: Type of the container entity (report, note, etc.) + :type entity_type: str + :param entity_id: ID of the container entity + :type entity_id: str + :param object_refs: List of object references to add + :type object_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_ref in object_refs: if version == 2: object_ref = object_ref["value"] if entity_type == "report": self.opencti.report.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "note": self.opencti.note.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "observed-data": self.opencti.observed_data.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "opinion": self.opencti.opinion.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "grouping": + self.opencti.grouping.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-incident": + self.opencti.case_incident.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rfi": + self.opencti.case_rfi.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rft": + self.opencti.case_rft.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "feedback": + self.opencti.feedback.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "task": + self.opencti.task.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) - def remove_object_refs(self, entity_type, id, object_refs, version=2): + def remove_object_refs(self, entity_type, entity_id, object_refs, version=2): + """Remove object references from a container entity. + + :param entity_type: Type of the container entity (report, note, etc.) + :type entity_type: str + :param entity_id: ID of the container entity + :type entity_id: str + :param object_refs: List of object references to remove + :type object_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_ref in object_refs: if version == 2: object_ref = object_ref["value"] if entity_type == "report": self.opencti.report.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "note": self.opencti.note.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "observed-data": self.opencti.observed_data.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "opinion": self.opencti.opinion.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "grouping": + self.opencti.grouping.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) + elif entity_type == "case-incident": + self.opencti.case_incident.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rfi": + self.opencti.case_rfi.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rft": + self.opencti.case_rft.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "feedback": + self.opencti.feedback.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "task": + self.opencti.task.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + + def add_labels(self, entity_type, entity_id, labels, version=2): + """Add labels to an entity. - def add_labels(self, entity_type, id, labels, version=2): + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param labels: List of labels to add + :type labels: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for label in labels: if version == 2: label = label["value"] if entity_type == "relationship": - self.opencti.stix_core_relationship.add_label(id=id, label_name=label) + self.opencti.stix_core_relationship.add_label( + id=entity_id, label_name=label + ) elif StixCyberObservableTypes.has_value(entity_type): - self.opencti.stix_cyber_observable.add_label(id=id, label_name=label) + self.opencti.stix_cyber_observable.add_label( + id=entity_id, label_name=label + ) else: - self.opencti.stix_domain_object.add_label(id=id, label_name=label) + self.opencti.stix_domain_object.add_label( + id=entity_id, label_name=label + ) - def remove_labels(self, entity_type, id, labels, version=2): + def remove_labels(self, entity_type, entity_id, labels, version=2): + """Remove labels from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param labels: List of labels to remove + :type labels: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for label in labels: if version == 2: label = label["value"] if entity_type == "relationship": self.opencti.stix_core_relationship.remove_label( - id=id, label_name=label + id=entity_id, label_name=label ) elif StixCyberObservableTypes.has_value(entity_type): - self.opencti.stix_cyber_observable.remove_label(id=id, label_name=label) + self.opencti.stix_cyber_observable.remove_label( + id=entity_id, label_name=label + ) else: - self.opencti.stix_domain_object.remove_label(id=id, label_name=label) + self.opencti.stix_domain_object.remove_label( + id=entity_id, label_name=label + ) + + def replace_created_by_ref(self, entity_type, entity_id, created_by_ref, version=2): + """Replace the created_by reference of an entity. - def replace_created_by_ref(self, entity_type, id, created_by_ref, version=2): + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param created_by_ref: New created_by reference + :type created_by_ref: str or list + :param version: Version of the patch format (default: 2) + :type version: int + """ if version == 2: created_by_ref = ( created_by_ref[0]["value"] if created_by_ref is not None else None ) if entity_type == "relationship": self.opencti.stix_core_relationship.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) elif entity_type == "sighting": self.opencti.stix_sighting_relationship.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) else: self.opencti.stix_domain_object.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) - def update_attribute(self, entity_type, id, input): + def update_attribute(self, entity_type, entity_id, field_input): + """Update an attribute of an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param field_input: Input containing the attribute update + :type field_input: list + """ # Relations if entity_type == "relationship": - self.opencti.stix_core_relationship.update_field(id=id, input=input) + self.opencti.stix_core_relationship.update_field( + id=entity_id, input=field_input + ) elif entity_type == "sighting": - self.opencti.stix_sighting_relationship.update_field(id=id, input=input) + self.opencti.stix_sighting_relationship.update_field( + id=entity_id, input=field_input + ) # Observables elif StixCyberObservableTypes.has_value(entity_type): - self.opencti.stix_cyber_observable.update_field(id=id, input=input) + self.opencti.stix_cyber_observable.update_field( + id=entity_id, input=field_input + ) # Meta elif entity_type == "marking-definition": - self.opencti.marking_definition.update_field(id=id, input=input) + self.opencti.marking_definition.update_field( + id=entity_id, input=field_input + ) elif entity_type == "label": - self.opencti.label.update_field(id=id, input=input) + self.opencti.label.update_field(id=entity_id, input=field_input) elif entity_type == "vocabulary": - self.opencti.vocabulary.update_field(id=id, input=input) + self.opencti.vocabulary.update_field(id=entity_id, input=field_input) elif entity_type == "kill-chain-phase": - self.opencti.kill_chain_phase.update_field(id=id, input=input) + self.opencti.kill_chain_phase.update_field(id=entity_id, input=field_input) elif entity_type == "external-reference": - self.opencti.external_reference.update_field(id=id, input=input) + self.opencti.external_reference.update_field( + id=entity_id, input=field_input + ) # Remaining stix domain else: - self.opencti.stix_domain_object.update_field(id=id, input=input) + self.opencti.stix_domain_object.update_field( + id=entity_id, input=field_input + ) def process_update(self, data): + """Process a STIX2 patch/update operation. + + :param data: Data containing x_opencti_patch operations + :type data: dict + """ try: - # Build the inputs fo update api + # Build the inputs for update api inputs = [] if "add" in data["x_opencti_patch"]: for key in data["x_opencti_patch"]["add"].keys(): @@ -283,15 +497,15 @@ def process_update(self, data): ): # ID replace is a side effect handled by the platform val = data["x_opencti_patch"]["replace"][key] current_val = val["current"] - if type(current_val) is list: + if isinstance(current_val, list): values = list( map( lambda x: ( x["value"] - if (type(current_val) is dict and "value" in x) + if (isinstance(x, dict) and "value" in x) else x ), - str(current_val), + current_val, ) ) inputs.append({"key": key, "value": values}) @@ -299,7 +513,8 @@ def process_update(self, data): values = ( current_val["value"] if ( - type(current_val) is dict and "value" in current_val + isinstance(current_val, dict) + and "value" in current_val ) else str(current_val) ) diff --git a/client-python/pycti/utils/opencti_stix2_utils.py b/client-python/pycti/utils/opencti_stix2_utils.py index c14344fb5d43..4ab417f92d65 100644 --- a/client-python/pycti/utils/opencti_stix2_utils.py +++ b/client-python/pycti/utils/opencti_stix2_utils.py @@ -1,7 +1,17 @@ +"""STIX2 utility functions and mappings for OpenCTI. + +This module provides utility classes and constants for working with STIX2 objects +in OpenCTI, including type mappings, pattern generation, and object reference counting. +""" + from typing import Any, Dict from stix2 import EqualityComparisonExpression, ObjectPath, ObservationExpression +# Aliases field constants +ALIASES_FIELD = "aliases" +X_OPENCTI_ALIASES_FIELD = "x_opencti_aliases" + SUPPORTED_INTERNAL_OBJECTS = [ "user", "group", @@ -132,7 +142,7 @@ "Process": ["pid"], "Software": ["name"], "Url": ["value"], - "User-Account": ["acount_login"], + "User-Account": ["account_login"], "Windows-Registry-Key": ["key"], "Windows-Registry-Value-Type": ["name"], "Hostname": ["value"], @@ -152,9 +162,10 @@ class OpenCTIStix2Utils: - """Utility class for STIX2 operations in OpenCTI + """Utility class for STIX2 operations in OpenCTI. - Provides helper methods for STIX2 conversions and pattern generation. + Provides helper methods for STIX2 conversions and pattern generation, + including type mappings, observable pattern creation, and reference counting. """ @staticmethod @@ -213,12 +224,13 @@ def generate_random_stix_id(stix_type): ) @staticmethod - def retrieveClassForMethod( - openCTIApiClient, entity: Dict, type_path: str, method: str + def retrieve_class_for_method( + opencti_api_client, entity: Dict, type_path: str, method: str ) -> Any: """Retrieve the appropriate API class for a given entity type and method. - :param openCTIApiClient: OpenCTI API client instance + :param opencti_api_client: OpenCTI API client instance + :type opencti_api_client: OpenCTIApiClient :param entity: Entity dictionary containing the type :type entity: Dict :param type_path: Path to the type field in the entity @@ -229,15 +241,46 @@ def retrieveClassForMethod( :rtype: Any """ if entity is not None and type_path in entity: - attributeName = entity[type_path].lower().replace("-", "_") - if hasattr(openCTIApiClient, attributeName): - attribute = getattr(openCTIApiClient, attributeName) + attribute_name = entity[type_path].lower().replace("-", "_") + if hasattr(opencti_api_client, attribute_name): + attribute = getattr(opencti_api_client, attribute_name) if hasattr(attribute, method): return attribute return None + @staticmethod + def retrieveClassForMethod( + openCTIApiClient, entity: Dict, type_path: str, method: str + ) -> Any: + """Retrieve the appropriate API class for a given entity type and method. + + .. deprecated:: + Use :meth:`retrieve_class_for_method` instead. + + :param openCTIApiClient: OpenCTI API client instance + :type openCTIApiClient: OpenCTIApiClient + :param entity: Entity dictionary containing the type + :type entity: Dict + :param type_path: Path to the type field in the entity + :type type_path: str + :param method: Name of the method to check for + :type method: str + :return: The API class that has the specified method, or None + :rtype: Any + """ + return OpenCTIStix2Utils.retrieve_class_for_method( + openCTIApiClient, entity, type_path, method + ) + @staticmethod def compute_object_refs_number(entity: Dict): + """Compute the number of object references in an entity. + + :param entity: Entity dictionary to analyze + :type entity: Dict + :return: Total number of references + :rtype: int + """ refs_number = 0 for key in list(entity.keys()): if key.endswith("_refs") and entity[key] is not None: @@ -249,3 +292,90 @@ def compute_object_refs_number(entity: Dict): elif key == "kill_chain_phases" and entity[key] is not None: refs_number += len(entity[key]) return refs_number + + +# Types that use x_opencti_aliases instead of aliases +# Based on opencti-graphql/src/schema/stixDomainObject.ts resolveAliasesField() +_X_OPENCTI_ALIASES_TYPES = frozenset( + ["course-of-action", "vulnerability", "grouping", "identity", "location"] +) + +# Types that support aliases (from STIX_DOMAIN_OBJECT_ALIASED in stixDomainObject.ts) +_STIX_ALIASED_TYPES = frozenset( + [ + "attack-pattern", + "campaign", + "channel", + "x-opencti-channel", + "course-of-action", + "event", + "x-opencti-event", + "grouping", + "identity", + "incident", + "infrastructure", + "intrusion-set", + "location", + "malware", + "narrative", + "x-opencti-narrative", + "threat-actor", + "tool", + "vulnerability", + ] +) + + +def resolve_aliases_field(stix_type: str) -> str: + """Resolve the correct aliases field name for a given STIX type. + + OpenCTI uses two different field names for aliases depending on the entity type: + - `aliases`: Standard STIX field used by most SDO types (Attack-Pattern, Campaign, + Infrastructure, Intrusion-Set, Malware, Threat-Actor-Group, Tool, Incident, etc.) + - `x_opencti_aliases`: OpenCTI extension field used by Course-Of-Action, Vulnerability, + Grouping, Identity types (Individual, Sector, System, Organization), and Location types + (Region, Country, Administrative-Area, City, Position) + + This mirrors the logic in opencti-graphql/src/schema/stixDomainObject.ts resolveAliasesField() + + Note: This function is case-insensitive. + + :param stix_type: The STIX object type (e.g., "malware", "vulnerability", "identity") + :type stix_type: str + :return: The aliases field name to use ("aliases" or "x_opencti_aliases") + :rtype: str + + Example: + >>> resolve_aliases_field("malware") + 'aliases' + >>> resolve_aliases_field("Vulnerability") + 'x_opencti_aliases' + >>> resolve_aliases_field("IDENTITY") + 'x_opencti_aliases' + """ + if stix_type.lower() in _X_OPENCTI_ALIASES_TYPES: + return X_OPENCTI_ALIASES_FIELD + return ALIASES_FIELD + + +def is_stix_object_aliased(stix_type: str) -> bool: + """Check if a STIX object type supports aliases. + + Returns True for entity types that have an aliases field in OpenCTI. + + Note: This function is case-insensitive. + + :param stix_type: The STIX object type (e.g., "malware", "indicator", "identity") + :type stix_type: str + :return: True if the type supports aliases, False otherwise + :rtype: bool + + Example: + >>> is_stix_object_aliased("malware") + True + >>> is_stix_object_aliased("Malware") + True + >>> is_stix_object_aliased("indicator") + False + """ + return stix_type.lower() in _STIX_ALIASED_TYPES diff --git a/client-python/requirements.txt b/client-python/requirements.txt index 01dfb3ce5ccf..7d73eba3d0c2 100644 --- a/client-python/requirements.txt +++ b/client-python/requirements.txt @@ -1,4 +1,5 @@ # Filigran +boto3~=1.38.27 datefinder~=0.7.3 pika~=1.3.0 python-magic~=0.4.27; sys_platform == 'linux' or sys_platform == 'darwin' diff --git a/client-python/setup.cfg b/client-python/setup.cfg index c27bbd3c3fca..aef63e7edf30 100644 --- a/client-python/setup.cfg +++ b/client-python/setup.cfg @@ -4,7 +4,7 @@ version = attr: pycti.__version__ author = Filigran author_email = contact@filigran.io maintainer = Filigran -url = https://github.com/OpenCTI-Platform/opencti +url = https://github.com/OpenCTI-Platform/opencti/client-python description = Python API client for OpenCTI. long_description = file: README.md long_description_content_type = text/markdown @@ -35,6 +35,7 @@ packages = include_package_data = True install_requires = # Filigran + boto3~=1.38.27 datefinder~=0.7.3 pika~=1.3.0 pydantic>=2.8.2, <3 @@ -58,7 +59,7 @@ install_requires = [options.extras_require] dev = black~=25.11.0 - build~=1.3.0 + build~=1.4.0 isort~=6.0.0 types-pytz~=2025.2.0.20250326 pre-commit~=4.5.0 @@ -69,6 +70,7 @@ dev = types-python-dateutil~=2.9.0 wheel~=0.45.1 doc = - autoapi~=2.0.1 - sphinx-autodoc-typehints~=3.2.0 - sphinx-rtd-theme~=3.0.2 + sphinx-autoapi>=3.0.0 + sphinx>=8.2,<9 + sphinx-autodoc-typehints>=3.0.0 + sphinx-rtd-theme>=3.0.0 diff --git a/client-python/tests/01-unit/utils/test_opencti_stix2.py b/client-python/tests/01-unit/utils/test_opencti_stix2.py index 63677856e6bf..f2ad0bdb89cf 100644 --- a/client-python/tests/01-unit/utils/test_opencti_stix2.py +++ b/client-python/tests/01-unit/utils/test_opencti_stix2.py @@ -113,4 +113,4 @@ def test_import_bundle_from_file(opencti_stix2: OpenCTIStix2, caplog) -> None: opencti_stix2.import_bundle_from_file("foo.txt") for record in caplog.records: assert record.levelname == "ERROR" - assert "The bundle file does not exists" in caplog.text + assert "The bundle file does not exist" in caplog.text diff --git a/docs/docs/administration/exclusion-lists.md b/docs/docs/administration/exclusion-lists.md index bb6591f3d356..87185e32c2c9 100644 --- a/docs/docs/administration/exclusion-lists.md +++ b/docs/docs/administration/exclusion-lists.md @@ -15,7 +15,11 @@ When creating a list, you can provide the following data: - Name - Description - Indicator observable types : this corresponds to the contents of the exclusion list -- Content : you can upload a file or copy/paste your content using the toggle button +- Content : you can upload a file or copy/paste your content using the toggle button + +**Details on file upload** +- File format expected: .txt +- Formatting of values within the file itself: please only add a flat list of value, one value per line. As said before, there are different indicator observable types possible : @@ -57,4 +61,4 @@ It is also possible to completely change the file, by toggling `Upload a file` . From now on, when an indicator is about to be created, if its pattern contains an observable value belonging to an exclusion list, then this indicator will not be created, with an error message of `Indicator of type stix is contained in exclusion list`. This applies regardless of the source of ingestion: Manual, Connectors, Feed ingestors (CSV, RSS, TAXII) and also Playbook. -![Exclusion lists indicator creation](./assets/exclusion-lists/exclusion-lists-indicator-creation.png) \ No newline at end of file +![Exclusion lists indicator creation](./assets/exclusion-lists/exclusion-lists-indicator-creation.png) diff --git a/docs/docs/deployment/configuration.md b/docs/docs/deployment/configuration.md index 7478a8990885..103427c34185 100644 --- a/docs/docs/deployment/configuration.md +++ b/docs/docs/deployment/configuration.md @@ -100,6 +100,8 @@ Here are the configuration keys, for both containers (environment variables) and | app:telemetry:metrics:exporter_otlp | APP__TELEMETRY__METRICS__EXPORTER_OTLP | | Port to expose the OTLP endpoint | | app:telemetry:metrics:exporter_prometheus | APP__TELEMETRY__METRICS__EXPORTER_PROMETHEUS | 14269 | Port to expose the Prometheus endpoint | +For a detailed list of exposed metrics, please refer to the [Telemetry](../deployment/telemetry.md) page. + #### Maps & references diff --git a/docs/docs/deployment/telemetry.md b/docs/docs/deployment/telemetry.md new file mode 100644 index 000000000000..bd44f260b499 --- /dev/null +++ b/docs/docs/deployment/telemetry.md @@ -0,0 +1,66 @@ +# Telemetry + +OpenCTI exposes metrics using the OpenTelemetry standard, which can be exported to various systems such as Prometheus or OTLP collectors. These metrics provide insights into the platform's performance, usage, and health. + +## Configuration + +To enable metrics, you need to configure the telemetry section in your platform configuration. See [Configuration](configuration.md#telemetry) for details on how to enable and configure exporters. + +## Available Metrics + +The following metrics are exposed by the OpenCTI API. + +| Metric Name | Type | Description | Unit | +| :--- | :--- | :--- | :--- | +| `opencti_sent_email` | Counter | Counts the total number of emails sent by the platform. | Count | +| `opencti_api_requests` | Counter | Counts the total number of API requests received. | Count | +| `opencti_api_errors` | Counter | Counts the total number of API errors encountered. | Count | +| `opencti_api_latency` | Histogram | Measures the latency of API query execution. | Milliseconds | +| `opencti_api_direct_bulk` | Gauge | Measures the size of bulks for direct ingestion (fast path). | Count | +| `opencti_api_side_bulk` | Gauge | Measures the size of bulks for absorption impacts (worker path). | Count | + +## Metric Attributes + +Metrics exported by OpenCTI include various attributes (labels) to provide granular context. + +### API Metrics Attributes +Applies to: `opencti_api_requests`, `opencti_api_errors`, `opencti_api_latency` + +| Attribute | Description | Example | +|:---|:---|:---| +| `operation` | The GraphQL operation type. | `query`, `mutation`, `subscription` | +| `name` | The name of the GraphQL operation. | `StixCoreObjectFind`, `Unspecified` | +| `status` | The outcome of the request. | `SUCCESS`, `ERROR` | +| `type` | The error type (only present if status is ERROR). | `AUTH_REQUIRED`, `FORBIDDEN_ACCESS` | +| `user_agent` | The client user agent initiating the request. | `Mozilla/5.0...`, `OpenCTI-Client` | + +### Email Metrics Attributes +Applies to: `opencti_sent_email` + +| Attribute | Description | Example | +|:---|:---|:---| +| `category` | The functional category of the email. | `hub-registration`, `dissemination`, `password-reset`, `notification` | +| `identifier` | The ID of the related entity (e.g., user, trigger, list). | `uuid-v4-string` | + +### Bulk Metrics Attributes +Applies to: `opencti_api_direct_bulk`, `opencti_api_side_bulk` + +| Attribute | Description | Example | +|:---|:---|:---| +| `type` | The context or source of the indexing operation. | `import`, `connector` | + +## Node.js Runtime Metrics + +In addition to the application-specific metrics above, OpenCTI also exposes standard Node.js runtime metrics provided by `opentelemetry-node-metrics`. These include metrics for: + +- **Process**: CPU usage, memory usage, uptime, etc. +- **Event Loop**: Lag, active handles, etc. +- **GC**: Garbage collection duration and counts. +- **Memory**: Heap usage, heap limits, etc. + +Common examples include: +- `process_cpu_user_seconds_total` +- `process_cpu_system_seconds_total` +- `process_resident_memory_bytes` +- `nodejs_eventloop_lag_seconds` +- `nodejs_gc_duration_seconds` diff --git a/docs/docs/development/platform.md b/docs/docs/development/platform.md index abb80571b3af..5d42fa5167b5 100644 --- a/docs/docs/development/platform.md +++ b/docs/docs/development/platform.md @@ -34,20 +34,6 @@ Fork and clone the git repositories - [https://github.com/OpenCTI-Platform/opencti/](https://github.com/OpenCTI-Platform/opencti/) - frontend / backend - [https://github.com/OpenCTI-Platform/connectors](https://github.com/OpenCTI-Platform/connectors) - connectors -- [https://github.com/OpenCTI-Platform/docker](https://github.com/OpenCTI-Platform/docker) - docker stack - -## Dependencies containers - -In development dependencies are deployed trough containers. -A development compose file is available in `~/opencti/opencti-platform/opencti-dev` - -```bash -cd ~/docker -#Start the stack in background -docker-compose -f ./docker-compose-dev.yml up -d -``` - -You now have all the dependencies of OpenCTI running and waiting for product to run. ## Backend / API @@ -86,9 +72,28 @@ At minimum adapt the admin part for the password and token. } ``` -### Install / start +### Install / start OpenCTI for development + +For development setup, you need to run both the backend and frontend. -Before starting the backend you need to install the nodejs modules +#### Start infrastructure with docker composer + +```bash +cd ~/opencti/opencti-platform/opencti-dev +docker compose up -d +``` + +You should have: +- elasticsearch +- kibana +- redis +- redis insight +- rabbitmq +- minio + +#### Start backend + +Before starting the backend you need to install the nodejs modules and be sure to have started docker compose stack. ```bash cd ~/opencti/opencti-platform/opencti-graphql @@ -114,23 +119,61 @@ The platform will start logging some interesting information {"category":"APP","level":"info","message":"[OPENCTI] API ready on port 4000","timestamp":"2023-07-02T16:37:12.382Z","version":"5.8.7"} ``` -If you want to start on another profile you can use the -e parameter. -For example here to use the profile.json configuration file. +OpenCTI api should be now up and running on [http://127.0.0.1:4000](http://127.0.0.1:4000). Note that without frontend started only api call are avlaible. Please follow next section for a complete development setup. + +#### Start frontend + +Before starting the frontend you need to install. + +```bash +cd ~/opencti/opencti-platform/opencti-front +yarn install +``` + +Then you can start the frontend ```bash -yarn start -e profile +cd ~/opencti/opencti-platform/opencti-front +yarn start ``` + +OpenCTI should be now up and running on [http://127.0.0.1:3000](http://127.0.0.1:3000) + +The frontend will start with some interesting information + +```log +[INFO] [default] compiling... +[INFO] [default] compiled documents: 1592 reader, 1072 normalization, 1596 operation text +[INFO] Compilation completed. +[INFO] Done. +[HPM] Proxy created: /stream -> http://localhost:4000 +[HPM] Proxy created: /storage -> http://localhost:4000 +[HPM] Proxy created: /taxii2 -> http://localhost:4000 +[HPM] Proxy created: /feeds -> http://localhost:4000 +[HPM] Proxy created: /graphql -> http://localhost:4000 +[HPM] Proxy created: /auth/** -> http://localhost:4000 +[HPM] Proxy created: /static/flags/** -> http://localhost:4000 +``` + ### Code check Before pushing your code you need to validate the syntax and ensure the testing will be validated. #### For validation -`yarn lint` +```bash +cd ~/opencti/opencti-platform/opencti-front +yarn lint +yarn check-ts + +cd ~/opencti/opencti-platform/opencti-graphql +yarn lint +yarn check-ts +``` -`yarn check-ts` +### For testing -#### For testing +#### Backend tests For starting the test you will need to create a test.json configuration file. You can use the same dependencies by only adapting all prefix for all dependencies. @@ -144,68 +187,40 @@ The file `vitest.config.test.ts` can be edited to run only a specific file patte `yarn test:dev` We also provide utility scripts to ease the development of new tests, especially integration tests that rely on the sample data -loaded after executing `00-inject/loader-test.ts`. +loaded after executing `tests/02-dataInjection`. To solely initialize the test database with this sample dataset run: `yarn test:dev:init` -And then, execute the following command to run the pattern specified in the file `vitest.config.test.ts`, or add a file name -to the command line to run only this test file. +And then, execute the following command to run only this test file. -`yarn test:dev:resume` +`yarn test:dev:resume ` This last command will NOT cleanup & initialize the test database and thus will be quicker to execute. -## Frontend - -### Install / start - -Before starting the backend you need to install the nodejs modules +Some yarn command are made for CI but can be used locally too: ```bash -cd ~/opencti/opencti-platform/opencti-front -yarn install -``` +cd ~/opencti/opencti-platform/opencti-graphql -Then you can simply start the frontend with the yarn start command +# Run unit tests, no need for elastic or redis +yarn test:ci-unit -```bash -cd ~/opencti/opencti-platform/opencti-front -yarn start -``` +# Run data injection + integration domain & resolvers test, but not sync part +yarn test:ci-integration -The frontend will start with some interesting information +# Run data injection + integration domain & resolvers test, including sync part +yarn test:ci-integration-sync -```log -[INFO] [default] compiling... -[INFO] [default] compiled documents: 1592 reader, 1072 normalization, 1596 operation text -[INFO] Compilation completed. -[INFO] Done. -[HPM] Proxy created: /stream -> http://localhost:4000 -[HPM] Proxy created: /storage -> http://localhost:4000 -[HPM] Proxy created: /taxii2 -> http://localhost:4000 -[HPM] Proxy created: /feeds -> http://localhost:4000 -[HPM] Proxy created: /graphql -> http://localhost:4000 -[HPM] Proxy created: /auth/** -> http://localhost:4000 -[HPM] Proxy created: /static/flags/** -> http://localhost:4000 +# Run data injection + rule tests +test:ci-rules-and-others ``` +Those command works also for a single file, for example to run only one unit test: -The web UI should be accessible on [http://127.0.0.1:3000](http://127.0.0.1:3000) - -### Code check - -Before pushing your code you need to validate the syntax and ensure the testing will be validated. - -#### For validation - -`yarn lint` - -`yarn check-ts` - -#### For testing - -`yarn test` +```bash +yarn test:ci-unit +``` ## Worker @@ -264,4 +279,4 @@ The Pull Request on ***opencti*** repository should be (issue or bug)/number + o The pull request on ***connector*** should refer to the opencti one by starting with "opencti/" and then the same name. Example: `opencti/issue/7062-contributing` -Note that if there are several matches, the first one is taken. So for example having `issue/7062-contributing` and `issue/7062` that are both marked as "multi-repository" is not a good idea. +Note that if there are several matches, the first one is taken. So for example having `issue/7062-contributing` and `issue/7062` that are both marked as "multi-repository" is not a good idea. \ No newline at end of file diff --git a/docs/docs/usage/assets/opencti-stream-export.png b/docs/docs/usage/assets/opencti-stream-export.png new file mode 100644 index 000000000000..758b6cac9d4a Binary files /dev/null and b/docs/docs/usage/assets/opencti-stream-export.png differ diff --git a/docs/docs/usage/assets/opencti-stream-import-icon.png b/docs/docs/usage/assets/opencti-stream-import-icon.png new file mode 100644 index 000000000000..9378bce4ff56 Binary files /dev/null and b/docs/docs/usage/assets/opencti-stream-import-icon.png differ diff --git a/docs/docs/usage/assets/taxii-feeds-export.png b/docs/docs/usage/assets/taxii-feeds-export.png new file mode 100644 index 000000000000..3b25993b6441 Binary files /dev/null and b/docs/docs/usage/assets/taxii-feeds-export.png differ diff --git a/docs/docs/usage/assets/taxii-feeds-import-icon.png b/docs/docs/usage/assets/taxii-feeds-import-icon.png new file mode 100644 index 000000000000..4aa77180b80e Binary files /dev/null and b/docs/docs/usage/assets/taxii-feeds-import-icon.png differ diff --git a/docs/docs/usage/import/internal-streams.md b/docs/docs/usage/import/internal-streams.md index 00bc81cba20b..9272385d7a89 100644 --- a/docs/docs/usage/import/internal-streams.md +++ b/docs/docs/usage/import/internal-streams.md @@ -35,3 +35,22 @@ Additional configuration options: - Use perfect synchronization: This option is specifically for synchronizing two platforms. If an imported entity already exists on the platform, the one from the stream will overwrite it. ![Live stream additional configuration](../assets/live-stream-additional-configuration.png) + +## Export an OpenCTI Stream + +You can export your existing OpenCTI Stream from the platform, making it easy to share your configuration with others. + +To export your OpenCTI Stream, click on "Export", in the burger menu. +![OpenCTI Stream export](../assets/opencti-stream-export.png) + +## Import an OpenCTI Stream + +If you have a JSON OpenCTI Stream file you can import it by clicking on the icon next to "Import from hub" +![OpenCTI Stream import button](../assets/opencti-stream-import-icon.png) + +When you click, you can select the desired file. After that, a drawer will open with the form pre-filled with the relevant information. +Add your desired platform's token. +By default, a user is already provided. + +You can select an OpenCTI Stream from the XTM Hub by clicking the ```Import from Hub``` button + diff --git a/docs/docs/usage/import/taxii-feed.md b/docs/docs/usage/import/taxii-feed.md index b6dfffc36d96..01e8bc9e4057 100644 --- a/docs/docs/usage/import/taxii-feed.md +++ b/docs/docs/usage/import/taxii-feed.md @@ -32,3 +32,20 @@ Additional configuration options: - Import from date: Specify the date of the oldest data to retrieve. Leave the field empty to import everything. ![TAXII feed configuration](../assets/taxii-feed-configuration.png) + +## Export a Taxii feed +You can export your existing taxii feed from the platform, making it easy to share your configuration with others. + +To export your taxii feed, click on "Export", in the burger menu. +![Taxii feed export](../assets/taxii-feeds-export.png) + +## Import a Taxii feed + +If you have a JSON Taxii feed file you can import it by clicking on the icon next to "Import from hub" +![Taxii feed import button](../assets/taxii-feeds-import-icon.png) + +When you click, you can select the desired file. After that, a drawer will open with the form pre-filled with the relevant information. +If necessary, configure the authentication type. By default, a user is already provided. + +You can select Taxii Feeds from the XTM Hub by clicking the ```Import from Hub``` button + diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8582b76b23ff..f1ceadc11788 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -116,6 +116,7 @@ nav: - Installation: deployment/installation.md - Configuration: deployment/configuration.md - Authentication: deployment/authentication.md + - Telemetry: deployment/telemetry.md - Upgrade: deployment/upgrade.md - Ecosystem: - Connectors: deployment/connectors.md diff --git a/opencti-platform/opencti-dev/1 - Backend start.run.xml b/opencti-platform/opencti-dev/1 - Backend start.run.xml index c2626fbb7fc7..a0fd0c825b3e 100644 --- a/opencti-platform/opencti-dev/1 - Backend start.run.xml +++ b/opencti-platform/opencti-dev/1 - Backend start.run.xml @@ -9,8 +9,7 @@ - - + diff --git a/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml b/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml index d68b5be3a76e..d56b5fc78f0a 100644 --- a/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml +++ b/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml @@ -9,8 +9,7 @@ - - + diff --git a/opencti-platform/opencti-dev/docker-compose.yml b/opencti-platform/opencti-dev/docker-compose.yml index 154a84be8f98..90efd09614b3 100644 --- a/opencti-platform/opencti-dev/docker-compose.yml +++ b/opencti-platform/opencti-dev/docker-compose.yml @@ -14,7 +14,7 @@ services: - 5540:5540 opencti-dev-elasticsearch: container_name: opencti-dev-elasticsearch - image: elasticsearch:8.19.9 + image: elasticsearch:8.19.10 volumes: - esdata:/usr/share/elasticsearch/data - essnapshots:/usr/share/elasticsearch/snapshots @@ -36,7 +36,7 @@ services: - 9300:9300 opencti-dev-kibana: container_name: opencti-dev-kibana - image: kibana:8.19.9 + image: kibana:8.19.10 environment: - ELASTICSEARCH_HOSTS=http://opencti-dev-elasticsearch:9200 restart: unless-stopped @@ -143,7 +143,7 @@ services: - "telemetry" opencti-dev-telemetry-otlp: container_name: opencti-telemetry-otlp - image: otel/opentelemetry-collector-contrib:0.142.0 + image: otel/opentelemetry-collector-contrib:0.143.1 restart: unless-stopped volumes: - "./otlp-config.yaml:/etc/config/otlp-config.yaml" @@ -162,8 +162,11 @@ services: image: quay.io/keycloak/keycloak:26.4.5 volumes: - keycloakdata:/opt/keycloak/data + - ./keycloak-configuration:/opt/keycloak/data/import container_name: opencti-dev-keycloak - command: start-dev + command: start-dev --import-realm --verbose + # use below command to export a configuration. Remember to remove admin user. + #command: export --dir /opt/keycloak/data/import/export --users same_file environment: KC_BOOTSTRAP_ADMIN_USERNAME: "admin" KC_BOOTSTRAP_ADMIN_PASSWORD: "admin" @@ -236,4 +239,4 @@ volumes: ossnapshots: driver: local keycloakdata: - driver: local + driver: local \ No newline at end of file diff --git a/opencti-platform/opencti-dev/keycloak-configuration/master-realm.json b/opencti-platform/opencti-dev/keycloak-configuration/master-realm.json new file mode 100644 index 000000000000..02d58cf2c560 --- /dev/null +++ b/opencti-platform/opencti-dev/keycloak-configuration/master-realm.json @@ -0,0 +1,2378 @@ +{ + "id" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "realm" : "master", + "displayName" : "Keycloak", + "displayNameHtml" : "
Keycloak
", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 600, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "77fb75cb-c77f-4cd8-b6b4-3fbc1bc291d7", + "name" : "connector", + "description" : "Connector group in OpenCTI", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { + "Group" : [ "Connector" ] + } + }, { + "id" : "ec38db3c-0bb9-4487-b3b7-18c26d9c16c8", + "name" : "administrator", + "description" : "Admin on OpenCTI", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { + "Group" : [ "Administrator" ] + } + }, { + "id" : "ab3bec5d-6a2a-410f-9d5c-8f6a0ded5f91", + "name" : "create-realm", + "description" : "${role_create-realm}", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "7aec3019-f8ab-4b67-b081-b40f6f48e105", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "93399c05-b52b-4c9b-a072-5326c81619ae", + "name" : "access-api", + "description" : "", + "composite" : true, + "composites" : { + "realm" : [ "admin", "access-api" ] + }, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "2f59b833-6118-4918-a0db-2dfd0695102a", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "63853894-7ec2-417f-b836-2f35e366f450", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "f116a4f1-b77c-4ce6-953f-069c8a02f70f", + "name" : "admin", + "description" : "${role_admin}", + "composite" : true, + "composites" : { + "realm" : [ "create-realm" ], + "client" : { + "master-realm" : [ "query-clients", "manage-realm", "impersonation", "view-identity-providers", "view-authorization", "manage-identity-providers", "create-client", "manage-authorization", "view-realm", "query-groups", "view-events", "query-realms", "manage-events", "query-users", "view-clients", "manage-clients", "view-users", "manage-users" ] + } + }, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + } ], + "client" : { + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "c4a0a33a-6ce1-4256-b5d9-18559143d499", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "e745d5a8-13ef-4f64-90b7-26e875760dad", + "attributes" : { } + } ], + "master-realm" : [ { + "id" : "f47a8ff0-5272-4e97-bb9f-f52a03b055a4", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "b60aa9c4-dfa9-4934-9698-e778fdee2b2b", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "b5806a1f-c343-4310-9b01-30a084186694", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "e5d63eb0-8576-4447-a011-b672c726e734", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "254755d0-8227-4238-9e5d-15671d7dd618", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "178f46b6-c47a-4d60-ab1f-c251f1b4e427", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "153f6722-3c24-4351-9632-714c7a60117f", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "60f0ab87-f188-4778-a6e1-bf2ed21dceb6", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "bd85692f-6343-475a-99ae-120a06ef2520", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "f6f0229d-22fd-4622-8eca-7987a24512fb", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "94c4bc84-0b6e-4260-8b1c-8aeb32e6c21f", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "f47f90eb-8b8a-49b6-b933-c5e27d9991b8", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "faad97e9-d824-445a-a857-867eebb8f40f", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "9eaeda81-8ea4-4969-9818-5e172d0dfab6", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "7f344b6d-38c1-4beb-9212-92544e70cf8c", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "17ffcd8d-db0c-404f-96f0-e527bef990b4", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "33f48ebc-8af3-4d72-9aec-b6ef48e72f96", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "6ac44d48-5504-4a66-9777-149be31c37b0", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + } ], + "openctioid" : [ { + "id" : "07686e6f-4730-4cdc-a077-e93896b7f106", + "name" : "uma_protection", + "composite" : false, + "clientRole" : true, + "containerId" : "7fbf7ab5-a72c-43a7-908d-b6b87bd40a8a", + "attributes" : { } + } ], + "account" : [ { + "id" : "546c6cb7-149b-4eab-873b-563b3fd79f34", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "7648fd9d-5a97-4264-a722-d26505772d1d", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "5559b563-5d8c-44c2-afdc-084b09761099", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "185ea8fe-3bcf-470f-97b0-080a2710de3e", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "e24a971b-14ef-4741-82c4-df0b4c1c82bd", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "135fb7fd-bc70-4e09-a248-679b5b7eda6c", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "d097b495-11b0-43a4-a0b5-b6fe259c49cf", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "de1362d0-d565-48ed-9b77-59f39aca2a41", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + } ], + "openctisaml" : [ ] + } + }, + "groups" : [ { + "id" : "4d41617e-138d-433a-bd7b-6509ef6cf926", + "name" : "Administrator", + "path" : "/Administrator", + "subGroups" : [ ], + "attributes" : { + "groups" : [ "Administrator" ] + }, + "realmRoles" : [ "administrator" ], + "clientRoles" : { } + }, { + "id" : "a36c25b5-731b-4b7d-ba57-4e2c697669ef", + "name" : "Connector", + "path" : "/Connector", + "subGroups" : [ ], + "attributes" : { + "groups" : [ "Connector" ] + }, + "realmRoles" : [ "connector" ], + "clientRoles" : { } + }, { + "id" : "fa3b5147-f0b5-44d2-89b7-89caefde187b", + "name" : "Default", + "path" : "/Default", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "c75bb716-3dfe-431f-881c-d55147bd5988", + "name" : "Externa org", + "path" : "/Externa org", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "bcd9546e-c4ff-4b96-8829-87d4113d4bfd", + "name" : "Filigran org", + "path" : "/Filigran org", + "subGroups" : [ ], + "attributes" : { + "org" : [ "Filigran" ], + "groups" : [ "Filigran" ] + }, + "realmRoles" : [ ], + "clientRoles" : { } + } ], + "defaultRole" : { + "id" : "7aec3019-f8ab-4b67-b081-b40f6f48e105", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256", "RS256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256", "RS256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "Yes", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "required", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "569ea160-4068-4545-9a71-0bc3e967cdb9", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "6df538a5-4df3-4dcd-b5da-0da84cb23cf5", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "d9f11af3-c863-4a55-90f6-9c8b9ad9e0f4", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "e745d5a8-13ef-4f64-90b7-26e875760dad", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "clientId" : "master-realm", + "name" : "master Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "7fbf7ab5-a72c-43a7-908d-b6b87bd40a8a", + "clientId" : "openctioid", + "name" : "openctioid", + "description" : "", + "rootUrl" : "http://localhost:4000", + "adminUrl" : "", + "baseUrl" : "http://localhost:4000", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "aOBaQuG6WVoQ4FKhOdIWOOdJp9e0M1Fc", + "redirectUris" : [ "*" ], + "webOrigins" : [ "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : true, + "authorizationServicesEnabled" : true, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "request.object.signature.alg" : "any", + "frontchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "+", + "oauth2.device.authorization.grant.enabled" : "false", + "use.jwks.url" : "false", + "backchannel.logout.revoke.offline.tokens" : "false", + "use.refresh.tokens" : "true", + "realm_client" : "false", + "oidc.ciba.grant.enabled" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "false", + "require.pushed.authorization.requests" : "false", + "request.object.encryption.enc" : "any", + "dpop.bound.access.tokens" : "false", + "id.token.as.detached.signature" : "false", + "client.secret.creation.time" : "1706016048", + "request.object.encryption.alg" : "any", + "client.introspection.response.allow.jwt.claim.enabled" : "false", + "standard.token.exchange.enabled" : "false", + "login_theme" : "keycloak", + "client.use.lightweight.access.token.enabled" : "false", + "request.object.required" : "not required", + "access.token.header.type.rfc9068" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "acr.loa.map" : "{}", + "display.on.consent.screen" : "true", + "token.response.type.bearer.lower-case" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "9e2a651a-b105-46dc-adce-d11484673e37", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "881fb6eb-0b39-4d5a-ba0a-90eac73da822", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "2745e2ec-be1e-45f0-800e-7bb0f21e52ca", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "service_account", "acr", "profile", "roles", "groups", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ], + "authorizationSettings" : { + "allowRemoteResourceManagement" : true, + "policyEnforcementMode" : "ENFORCING", + "resources" : [ { + "name" : "Default Resource", + "type" : "urn:openctioid:resources:default", + "ownerManagedAccess" : false, + "attributes" : { }, + "uris" : [ "/*" ] + } ], + "policies" : [ ], + "scopes" : [ ], + "decisionStrategy" : "UNANIMOUS" + } + }, { + "id" : "a8df3b0e-6b9c-4593-9e25-a181071b4f22", + "clientId" : "openctisaml", + "name" : "openctisaml", + "description" : "", + "rootUrl" : "http://localhost:4000", + "adminUrl" : "", + "baseUrl" : "http://localhost:4000", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : true, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : true, + "protocol" : "saml", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "true", + "saml.encrypt" : "false", + "post.logout.redirect.uris" : "+", + "saml.server.signature" : "true", + "saml.server.signature.keyinfo.ext" : "false", + "saml.signing.certificate" : "MIICpTCCAY0CBgGNNymQaDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtvcGVuY3Rpc2FtbDAeFw0yNDAxMjMxNjI5NDdaFw0zNDAxMjMxNjMxMjdaMBYxFDASBgNVBAMMC29wZW5jdGlzYW1sMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuLaYBqul7TnRmi3dsi+Azcjrpb1XPfRKPuFmzjQIs5a3wbPHk1J0jt2Mt2nA6h7pDIzAkWPRukbhXEPf37fZiRtllmLohJCIq7WZvvKt4wLho0mZrnwR5q6zUR+wNRSYdozxjdvGl9KbF+Ip6chs1STAktcCrOhDAjMcUyIv2Gv4EtN3qxiORMZ5ndsEf4C6GJoOwU9fr/aE4dslrUCNUjqLKr4ZTjO98NVdlYoW6lG9vT+ALbSQvcWCsovgSKxzOuRUK/cBQUeeLuWpXaMP4dIldyEpt/ETVfW/MEwWHj+HkrkeZN3FqqYO/fvyIkWinOqC3Gd8MM7lM9WEUAbM1wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBiEL1CwH0QaAsx4Tk6TRbsIXXNs7UQRidLqqmipjE2tr9J5G0m1uJxFzhwtbZZmF4ucR9PaGp9HXTvgWFWAydLfcsj0JdglGPz+hWJ3j1STDzjk7DCOgoFC+gUMbT3W4+15FVlVIQA9I78rEXH3j1Ffj+LhyLDbsGPKNSA+E5iqcvKoTdh+uQMTHhq395TmbWWGobTNd/h3ybTDKICw78WaH+V7m2vUgF1QoPnhqAVD+lQlc1C8nPnU3H4lQ6VTgX8K1ZAqZq1bOSLR52WfTahcL/H3uZFXLhSB+zcYQ3oYddCpo/Qoqd1vHyVmbMilXlviyFToAbhwcU/avPeW9ag", + "realm_client" : "false", + "saml.artifact.binding.identifier" : "PxT8qlPBBHViShc4aoU4VotNxIY=", + "saml.artifact.binding" : "false", + "saml.signature.algorithm" : "RSA_SHA256", + "saml.useMetadataDescriptorUrl" : "false", + "saml_force_name_id_format" : "false", + "saml.client.signature" : "false", + "saml.authnstatement" : "true", + "display.on.consent.screen" : "false", + "saml_name_id_format" : "username", + "saml.signing.private.key" : "MIIEpAIBAAKCAQEAuLaYBqul7TnRmi3dsi+Azcjrpb1XPfRKPuFmzjQIs5a3wbPHk1J0jt2Mt2nA6h7pDIzAkWPRukbhXEPf37fZiRtllmLohJCIq7WZvvKt4wLho0mZrnwR5q6zUR+wNRSYdozxjdvGl9KbF+Ip6chs1STAktcCrOhDAjMcUyIv2Gv4EtN3qxiORMZ5ndsEf4C6GJoOwU9fr/aE4dslrUCNUjqLKr4ZTjO98NVdlYoW6lG9vT+ALbSQvcWCsovgSKxzOuRUK/cBQUeeLuWpXaMP4dIldyEpt/ETVfW/MEwWHj+HkrkeZN3FqqYO/fvyIkWinOqC3Gd8MM7lM9WEUAbM1wIDAQABAoIBACXvAcuk5p+QVzZVowjb7pTUZWiVONx1VeYR/j3su7i+BCDYnezoax2H7EUih8bM8ElugoGZQVIDCncbTVexdxBMOxGmYGARGrBAzEFFr2ZGijYxgEkwG7EHQbYwTsyn3SPDkDv03ZUCYG2IOdlUGt2u/YlqdJcz38cM3g1IoRDNySq4A9gW65kLxR/JK6Ux7pO0IC8Rpdx38Ok2MeZXfb9uGgFxprrKjuPaXoiLPQxLj/+8OZnP/5WOWIXi55VxxH0k3T9/7kM/eBRuDg1tksj2RKWbhxq8m1nLFN508UJLfAF5TQMNH3S4MJwx5eU8LYxY7yW70vTT5cSBmTtOe4ECgYEA/mApckFvF46asi+dpEOkteT/qQela0rcSguY++b7KL1G8zcjakxPFZViqFbcjKCHddcyiqSMnB1Aip8UaG/lWU21LY7CxJqbFJZhAhTNTgZSGf2WIb2LD6oz6+Pm/Z6V7AInNc0uD9q5wMnZRbS+KbM7lq3R/9Ff7RS7p4DQpJcCgYEAueSNU8HT2Qymuc0cCxvugkckn4aKE8bkTIEZwtL4L6EvSpV6J6XAVYh79rdBL9WjUiBJr5iITpqCm+Ws9urqtZgFi4f5OFlXPgdBGgcC6I3uwL6kX0wZF8SqgyxA4rj6FCNPHhdU+L/H/ReJoYujOkleDll56WMgU4Inbea+4cECgYEA3CY/WIn26m1ZxuLczQBZ+a5R8WkTqfLlChRVd5WlQtHlKLNMrD+UpjpeYxCh2fdIpRz0ufbFVoseg5o/4E8PMCXHqsEGIX8ovj2TgWidcmyX+7RzjYnsY0dLnljkXhU07UfDxZVoywHih05qAyD0/0QGS1buCzeajKXH7qTWbcsCgYAKRVN9rjbrRiSsHWYQQxHRhubCHafhYdrZU0S+G/P0hb5cK5gdOq4+y5S10/g5EV+9uOT5W78kQKs4u97roZ0oPWcJB5FAiMcmOTZinsKNYNIxOhdQ4J5+TrJxHu/S1w+SL0U+z2E1gTsmg7dqApIZNVaKCm2O9JgjpQxSqS4gwQKBgQDX9VuC1tPfjjWvmkLbrVzgUZJypOZFZbFBK/mYKqY7N6uLBqOx+6134S0PTSzI+9q/Xig68s6hrVjqxg0Jl7VLrhnZQNa0UE9pCsINkkhccUHF1JaUGtdai1iYZSAaMmK2ABpazx4rKv6daJNmpNs26TWZPctImhPzvvZ7UJyN8Q==", + "saml.allow.ecp.flow" : "false", + "saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#", + "saml.onetimeuse.condition" : "false", + "saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer" : "NONE" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "7e6b91f5-9234-4ba3-8bee-79b26a35cf91", + "name" : "organization", + "protocol" : "saml", + "protocolMapper" : "saml-organization-membership-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "8ecef93d-4bdb-45e6-be70-725c2e9d19ce", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ], + "defaultClientScopes" : [ "saml_organization", "role_list", "groups_saml" ], + "optionalClientScopes" : [ ] + }, { + "id" : "44a1c418-f163-4d41-8331-613be37e9d8b", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/master/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/master/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "93dd9f3f-271e-4e96-81f7-e916272c0bce", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "da296e58-f3d8-40e9-a00d-d0af7c4e173d", + "name" : "role", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "9e75c090-e975-4620-8b63-f6d8f1091e39", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "51867b26-fcd8-4103-9c60-9c235ce4ab86", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "3b838ed9-bb99-4f16-8839-ae355ee361b1", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "4e9affc2-8410-45b0-b402-b76b6d8d4d01", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + } ] + }, { + "id" : "bda1114e-c755-4d45-8620-43df51384168", + "name" : "audience", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "41f474a6-acc3-4cc7-ad1d-5bfea9278279", + "name" : "faudience", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "false", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "false" + } + } ] + }, { + "id" : "8aee4b94-5c57-4839-b38c-633e3130f9f7", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "c32d1363-3bf2-4417-95aa-3471e5cd1d10", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "7395ed6f-c1e0-445f-89dc-06eda587817a", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "db03d510-4eb5-4613-b0f1-d2e0f01df0a8", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "381269ef-39ca-4dfe-9bc8-85449e47c28b", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "cbf26ccb-ba1d-42ce-ac71-dfaafff27512", + "name" : "groups", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "5052818b-7daf-4e4b-8cfe-f76c0d330b22", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "9d78d692-acd4-4f31-af27-de80615b7ee8", + "name" : "member", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-group-membership-mapper", + "consentRequired" : false, + "config" : { + "full.path" : "true", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "groups" + } + } ] + }, { + "id" : "030ba4c2-6ce0-461d-a936-6964517db933", + "name" : "organization", + "description" : "Additional claims about the organization a subject belongs to", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${organizationScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "910d5b09-d06e-4df1-9db0-eaa9b93ff6ad", + "name" : "organization", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-organization-membership-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "organization", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "44bb6371-ccf9-4816-8a0f-030ed40a61e0", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4dfc3c79-878e-43e1-937d-10b0af38cb83", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "a6e73ad4-cd5d-4908-9f86-955d23abcccc", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "fd2bc4ba-ec12-4b85-80e1-48ad0b8bee02", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "92b05a6a-be4c-4f05-97fb-1fea2f64d55b", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "d50f198d-e411-4aa9-8eae-212aa58eaae3", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "d4fc6db4-28d6-4f4e-8c04-d88a9caf54cf", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "3d3dcaca-b2e4-4ebc-b818-4ec25416da7d", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "352a7146-f68b-4e27-a6e3-4456dee1407c", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "ceb78da2-bc33-4433-909d-2e5085aab469", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "e64a8a8f-c54f-46bd-96cd-22667463b2cc", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "487171f9-578f-4632-8345-dd1fab38aa21", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "7deacf75-8309-458f-b7fe-5fc2c3fe4c40", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "05e413a2-3454-4e6d-a5e2-53c46c71a729", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "4fac2672-e02e-450e-a870-a332b9956e55", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "9a102d32-9b89-4909-bafe-537d9438dca2", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "e623e917-3590-4874-9605-bc61895879fd", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "f9c02866-2a03-4c9f-aa6b-efcfc0ba699c", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "91129706-c63c-4c07-849a-6e073c5d812b", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "f44c3ab1-62d8-4fee-8a5d-8b601f3b6661", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "8a2069f0-d6b6-4535-b8bf-0587c4625e35", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "be76de0b-f5dc-47ce-97cb-23c974ad57d2", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "7088158f-3e57-4caa-bb30-b3361c3894e7", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4799b07e-ee99-4d5d-bbf0-b1532bb79cd6", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "9518f5d0-32d9-4fd0-93f4-b545b2033fc1", + "name" : "groups_saml", + "description" : "", + "protocol" : "saml", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "6ae57640-0146-4573-80cd-db85fee07561", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "8db7e08f-5e72-4c9b-8599-64317eaa2106", + "name" : "groups", + "protocol" : "saml", + "protocolMapper" : "saml-group-membership-mapper", + "consentRequired" : false, + "config" : { + "single" : "true", + "attribute.nameformat" : "Basic", + "full.path" : "true", + "attribute.name" : "member" + } + } ] + }, { + "id" : "629ff521-47a2-430f-8028-7c2131cefb19", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "494f4625-207f-4443-9588-0bb18c78994e", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "e9cf797e-3cbb-428d-a86b-ab66704ff97a", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "f88239ff-eb0b-4c9c-bd5a-6f8bc0e59bdf", + "name" : "saml_organization", + "description" : "Organization Membership", + "protocol" : "saml", + "attributes" : { + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1581661c-70ac-42cd-b0e5-69edb9787cce", + "name" : "organization", + "protocol" : "saml", + "protocolMapper" : "saml-organization-membership-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "4b6c3d8d-5bcf-4da1-860d-31af9cfafb5e", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "c2f71464-e8e1-4228-84aa-5524855ab7ed", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "f8dfd71c-22c8-4a4e-9772-76c09d3da621", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "3de52bca-c561-449b-ae03-4a87ef206c76", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "e7c5baf7-646b-4b8a-8b40-252f4e4a65a2", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "f2ad8f28-bb7d-4817-b64c-d7207b379a1f", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "fd56168f-331f-48d0-924a-e8f795106e14", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1edc477e-b7a4-4605-8337-cc382522563d", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "75e2de97-3c32-4e6b-a2bb-60481d510f8b", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "saml_organization", "profile", "email", "roles", "web-origins", "acr", "basic", "groups", "audience", "organization", "role", "groups_saml" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "d52ba708-adbc-4f2a-9d7f-0212f2554797", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "f6e3b60d-0cbf-4637-adf0-c1fa49fa2499", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper" ] + } + }, { + "id" : "220517d7-46f7-413e-b001-955d5a78b3da", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "6f7a251c-54d8-4acc-9dea-d20012b9cb09", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "fe27f456-4380-4d18-9a6f-772a29bddcbf", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "f441ac3b-a8c2-4cad-91d8-994c3bb2fe2b", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "d6ad7142-6a18-465e-a3c3-b2f7230fcab3", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "04814e09-5473-4ef5-b614-5deffff80287", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "2e83948f-a9f2-422e-b342-7645a5736770", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"groups\",\"displayName\":\"${groups}\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "62078160-d785-4eb9-a8e8-6ed7babe5ce8", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAoS2BNIPycClPSHU6odlskAfetfrcozoSXFBx/h7s5A1o2klBi7jyWkfuC/R8FMVBFZ07sz12wXXBseBGEsnwOMSt/EJ4z5C8B6TRqF+Y0GuRcZI4xjNvgSj5oQiSPtuGZf/7bgYg1l8vPU0TjPCpHgQ8hepJofB96t3P7NCrX2UzW7mhwJDf1UP3u1BCf+eT0vYvQN8pSiSY7SyGMoHeh3U7LospDh0ONSzGA3dqt6fpWMTmchNVhRwP23rLZzFEcbrPMug22VC8wIxHkoD2HHCO8FYQDDC4NUMZiNAgf11ScbhbhQOofdMacyjL93+IGNrYVsbyMAx6zoAtHcaEvQIDAQABAoIBABjEnGFMkXno7zUMRrlOZx9vBiti5Hr01pN3DT1m1TJqWR2KOldVUXDMMumFZXfvpAPFSMJPhKtQBDboZvxGKfMfTjlAob4L2Mk367v7DMmibhRMywqyJsVrXYkAfzDHnUyZXLLYoD0xHVpHOOTkqqhhMEfH7A7FnSP1eBlFK8CZ1tdZ3I3qcKOtPDY0J9KuSh0K6Keq2Bdh2bpDzywSwCpF+4EtF9z/oAsGmzX6l3g5p0OzEIymknKP8RL7SLTONfFZUn8XmUKgEruaYTIB5HlnKuN6/RK14mzRoDb6ZFWi4nuwew5YeZHFU9sku84tznZMK5W0dVIRghTLCmTsW6UCgYEA4Zrz3+ixI2XmNK+0c/AVps+TetUvSvGHsg0MrdPM5Fyq42pK4I8zc0rP3BLZPpyorhanJNLRfFxVHf0KyMkyAOJK5L5Dq1jsi93MTs7E5RubSlr6xCYMoP85D75+wQdpioTeZLEQzfLxyz0cHKeoKzV0GGFZpOgmF7P/J/b4SZ8CgYEAtuR4E9ov8is+wL8WJn35ADjRHAQW+euqkyQBnDr4FCUxwU3u9xMfD7OeQus1YbHIUBfi09rp6264xRto7RWmH8FhOi+m2WGZkBcPdrVe0Lw1oDrQvvsWdl0ol3afEoxiTU/wCOfWsOYqC025vgGvpCl3h5tWzhF0qAibBP//DCMCgYEAhEEFJEbRyRGMYWh7XdfpqW9YYpKk9ccfqEY0H5bhLyIP399I49mu7LB3p+i4yBaoX93RwCmCOugZ0cmsT8Z6dMAz7WKIYuNvUBMHGU/nZvPHFlC9Xs17a8oSlmMzBU+mFFkN0nNmiYUZL+60EyxJzoK4ey5ekeixpAWV18TVCrsCgYBHBoTnWMVj641sNwk4G5XWkzoKAkCWAAJ3L/V8IZ5z3FntMwHJa+CVLXEZldReQzeCezQ4h/xt1MrmqRVfdRfVnzjN6vGF3BRR09LKi/btYxoERrMIZ+Q6RUVdRNDT2DbYxWF3Y+mJO2k6iI3Ij0kRnJTx0c6tKjQ+iVECaRQ4NwKBgQChV8s+kXm5wCNWpyFsV75rJ1x7QO4PeS+SNnf5N+ks8P0BejsUmv+7rZX/r3dHQ5cALHjcEJcVJ3wZAgeqHK5vKrbsLjf0Lp1gy1oei5MoNc804EsyV0V+ol7Iajva/plUS5iePRd87vsl7ar0KnZZnh+xVL4ijpa8JMEaPropoA==" ], + "certificate" : [ "MIICmzCCAYMCBgGbiFigTTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjYwMTA0MDkyOTI4WhcNMzYwMTA0MDkzMTA4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChLYE0g/JwKU9IdTqh2WyQB961+tyjOhJcUHH+HuzkDWjaSUGLuPJaR+4L9HwUxUEVnTuzPXbBdcGx4EYSyfA4xK38QnjPkLwHpNGoX5jQa5FxkjjGM2+BKPmhCJI+24Zl//tuBiDWXy89TROM8KkeBDyF6kmh8H3q3c/s0KtfZTNbuaHAkN/VQ/e7UEJ/55PS9i9A3ylKJJjtLIYygd6HdTsuiykOHQ41LMYDd2q3p+lYxOZyE1WFHA/bestnMURxus8y6DbZULzAjEeSgPYccI7wVhAMMLg1QxmI0CB/XVJxuFuFA6h90xpzKMv3f4gY2thWxvIwDHrOgC0dxoS9AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGmrQMXqDYHaXFkU8Nz9jUlwIqlc1Pg6nsIBAjOfSvQc+m64QQerwCeFMox7Gwj6t1JRl/dfpkxn+r5HP6SN4jzZXAua1wBZg3f6tdwuOhOUHRt93i0re+8cf4yoQfZdPkvXuSewjpZj7NfDqlQ2JmsvB5SFwd9Hpi6nTZ7vOGaqurlET40KP2eZwdDDxb1YaNO3vm0mjdhAWx+1i0UEKxtcKN/mnEF9qLMawKXunmLzrbLzyo/ktI+f1eZ7l0rQMvPuyfwk3PEU0nPQWyAnCKYh6dsLlzVCFNYiE9Fx69gTFySsY0TZbeGImp5HlFm7feAELwoyUqyxGZ+CCUQK7vo=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "b8bf7983-7205-4f19-8332-0e9119f68328", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "06da48aa-c992-4a61-bc1a-c2ffebce653b" ], + "secret" : [ "df1honNeAbVOFk0DlJxgfraQQUJq_F1trKKPfcgzbMjmtFVHpIA9KZg14JBAXnkMZFxWPYdnX4_etlrk9nDnkZSNWJBzzihTlcWXnCDyFU5xGtgQJg5AjFahHx7jJJZt-B07g3kWQAmci87BXhfKLU02X2vF8d5cJWW-cwXD57U" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "674de2a5-7694-4c31-abf1-b2f0e8eed268", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "736676a9-b1c1-4e28-bde9-e3281935d5fe" ], + "secret" : [ "ylLCqe6aQzRwrZrWgLmHQw" ], + "priority" : [ "100" ] + } + }, { + "id" : "3ab3cd9a-47b7-40ec-9f8b-818abf85388d", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEA5bgJe69O3F8BKQoJ14jsZYIcp23//zOIeHd+WMV/YCDvdfJB5A1DdEpZvo0+nCU/SfxWXRfetMXty9G7h72GrH1sFP2BNnGYYga7L/gIN6evedCyqt0/dRrbxcrywDW0m+ZQKGNvRwUw2UyJnvt681TxaZfi0DM9Vmo8ecuSo7c3XSA4F1+1Vhs1InLlTNTtYRmDaggGZLDoFG0T/Vln8RnLNNbhTDLW6/EHSh9UvJLyXTF6h0vb6s98uxHjPju651N3rXu7W7S42+Bo3Y20CvluiXB/7zYOVMCRLEf2UDpyPKE/CvDzDCAmovkunE7OoOS/PlBTk5TP2DCuSxWyOQIDAQABAoIBACM1MDrlNQyAgSCFmdmPck/if7rHmLdH8jAofFRlvK6CjcEBhsjJoZ+Gr7eNVV3ANbGEu8Xb3TXOqjIVbI0S5ru0I2caLX4nLehESNdCyBujlzjv3Bpk+49atPldMBR2hr5oL7vo9YqoiIKdgPA9cewuhiudbFVlrQ58DNXL9iciKMOu8RoiigBeGNI+NvnBtvUEGUeFFh8cbASXRS/KLju9MdfyDwJke5iMdJ+mLIIRY4mj26rtkyU509++zB1o1kbRDJLIY7i1GJctXWj0JXjpQJHibiraiYmCmLBLItYfTbwDeV+Hk8IiQL3Kwf9uNz/83BuMCcPBNyGUE3M/VCsCgYEA8tuiGBRt1XPtsMbxSoj10fjP4nyOW+hv4ewGFShAZo1hXZ+aTlVwJ3U+8SRM/NhyBtdZCZxyxqEKZ64fsab0eEGesy8p1PyXY21zHWCSz26MGo8245JVgk2xzWHNnePqVApHNvBWL5ji0aA42bxG0QCtpcWq1Xe46bJEqL5yvdcCgYEA8iZiuD2s0K7iL8WETPFL52Ik2A1WP63F+LacMQ1c2aHsGZngru/KRJj8jc35fJkwG3mgl5YCe/dIgFJd/ida8ajleagHl/jrA/6oGlrNFS/fvOByAuNaFSGmRwlybbKjvjtKyycU3MQE3qZgDG/8mzrNIventvmELVZhIb31bm8CgYEAwyAUrKQ9WzM51r8BP5GrcHkpDiBlgRLQUgUBHh6pH7yYblC9+hJLemiJmdZAxSZi93wu3boFvfHGGmecr3AlHDoc+Hr8cVRBjnFx/DiLvH5lszE+OLpqWbdzFEFwo1tr+voHHe0cUkHUe/jvtIHBpEuXOYoIKFt5Bstkvu/F4SECgYEAnc+kcLxJHyuKX2XTRi3PsPlGV8PBXPjMV73y5wj1ZSRg8YJWasv9v10Q4v3ExY40SwOmRIRQFChhiLelaBiP7YSMIQ//+uwPhef1+E8K7u7nWnnP88/linYGnq8qcxGEI1sS8HkE4KejINO/Lvvg3e2heVN7awUMPlvYK5xYEmMCgYBsAh1yi0tEIfBa98oJIxfC4OZgUZQUaeOb3iQgiFWkcVdGQDZz856spGPXDD8ek9/0WMDKMdEYfjko6yqsijDP/09i8lqQ9e0qz+8MmxizBhAZ5XxYgO7eJ7V/XkTc8hNc54PJL555N8I0dKit3Qqrmxkby78vOaJVZ7TZjGFDOA==" ], + "certificate" : [ "MIICmzCCAYMCBgGbiFigqTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjYwMTA0MDkyOTI4WhcNMzYwMTA0MDkzMTA4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDluAl7r07cXwEpCgnXiOxlghynbf//M4h4d35YxX9gIO918kHkDUN0Slm+jT6cJT9J/FZdF960xe3L0buHvYasfWwU/YE2cZhiBrsv+Ag3p6950LKq3T91GtvFyvLANbSb5lAoY29HBTDZTIme+3rzVPFpl+LQMz1Wajx5y5KjtzddIDgXX7VWGzUicuVM1O1hGYNqCAZksOgUbRP9WWfxGcs01uFMMtbr8QdKH1S8kvJdMXqHS9vqz3y7EeM+O7rnU3ete7tbtLjb4GjdjbQK+W6JcH/vNg5UwJEsR/ZQOnI8oT8K8PMMICai+S6cTs6g5L8+UFOTlM/YMK5LFbI5AgMBAAEwDQYJKoZIhvcNAQELBQADggEBADf9Aj0607hPwHkGh9IdGgZm4Xs2E5v9Bzh7qtfxInJAErhZmChc/6AwNwkCIsGXPb6LRK+BGu2EIY8G3WvoBWQK2s16OSSH3QENA+GWIVLqHibEY+oLfoFmtyTGo/5TqXHZ/XlLFxyn+6BUpdZyr4p406uhKx2PCYHgDziLsDo1c/56AnjUbehEI30gCGCI3F0NYfxYzsnCJGK8ZzOzU2h9/Bfemt0t9gQUcwVJn6QHvKnT5mNO1qzJPRBwpz1GiB9P5+UT0SAnt+oxIAH5Gtwr0qI5sb9ZIAaS2FJut/cqiTsKzSjpRBE/AwsyU915Eyygy3HUjVxTxqH0BZga178=" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "authenticationFlows" : [ { + "id" : "729de913-8bd6-47a4-ae43-0d0c6ee8c628", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "c9430684-3df1-478a-b46c-fcd49d99ca43", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "b2cc065d-7f84-4d67-8e8c-346e26f4283b", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "f2f73298-77ab-406c-9bfe-1107c4b0e696", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "8bbcd9b4-3272-42d5-8dd9-51591faba89f", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "62a91d8b-6617-43a6-86c0-9898e1f02a70", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "4c6aef61-2d74-49c5-bd8a-7b2dff39bb19", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "e792b361-c5b3-4a78-b93c-0ea2fc38532c", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "5b5a1554-60e2-4154-9b35-4e398a5e81b8", + "alias" : "browser", + "description" : "Browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "ba17c695-ab27-4581-8060-e13d732c0e7a", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "6db9ee9c-e45f-4688-9f88-f56fd6855ace", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "8e0dc68b-3c2b-4b87-b080-4fb623fa367a", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "9552ffe0-ecbb-4bc0-bd10-01c8d2f25e95", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "05f817ff-d674-412b-b478-04b97ca2ace8", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "c53d4ea3-e0dd-468f-b90c-a251ef5f83c6", + "alias" : "registration", + "description" : "Registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "6e9ac60c-7d98-46c2-bf51-1392aa5be84b", + "alias" : "registration form", + "description" : "Registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "21e2a653-5c04-411b-9bfd-9cfe5deaf577", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "6f707d1e-ae0c-4bbb-9840-b27214fd2db5", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "7fed3aaf-9c98-44a5-a22b-4aeb900e4a2a", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "b9955a02-a0e2-449b-886c-f06740e4830e", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : false, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "idp_link", + "name" : "Linking Identity Provider", + "providerId" : "idp_link", + "enabled" : true, + "defaultAction" : false, + "priority" : 120, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "actionTokenGeneratedByUserLifespan.verify-email" : "", + "actionTokenGeneratedByUserLifespan.idp-verify-account-via-email" : "", + "clientOfflineSessionIdleTimeout" : "0", + "actionTokenGeneratedByUserLifespan.execute-actions" : "", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "shortVerificationUri" : "", + "actionTokenGeneratedByUserLifespan.reset-credentials" : "" + }, + "keycloakVersion" : "26.4.5", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "verifiableCredentialsEnabled" : false, + "adminPermissionsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json b/opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json new file mode 100644 index 000000000000..d3eb4438fd37 --- /dev/null +++ b/opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json @@ -0,0 +1,146 @@ +{ + "realm" : "master", + "users" : [ { + "id" : "3be9316d-18f3-41f1-922b-3a6487b9e2c9", + "username" : "admin@external.com", + "email" : "admin@external.com", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554373137, + "totp" : false, + "credentials" : [ { + "id" : "8e297757-4d97-43cc-b4d2-6133c59d63ab", + "type" : "password", + "createdDate" : 1767598129421, + "secretData" : "{\"value\":\"88PeB64jNwArxy0NuneXrlvHQo4fZ6DZvCk3uwxFJOg=\",\"salt\":\"gF09TJwGHhd8t573OKoObg==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Administrator" ] + }, { + "id" : "9b4b2bf0-1bc3-4dad-9b06-4723426bdcb2", + "username" : "admin@filigran.io", + "email" : "admin@filigran.io", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554290159, + "totp" : false, + "credentials" : [ { + "id" : "5de4c4e6-35ea-4364-8541-1870bf56d05d", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554302071, + "secretData" : "{\"value\":\"ylqptqYbdPmLn1YQztQbIwMzUmIaLl771QzRe4NGTi8=\",\"salt\":\"oH2M58Jiioa6WG3rnDaO2w==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Administrator", "/Filigran org" ] + }, { + "id" : "a88774e3-b4fa-4ef3-8285-e430e08f32b4", + "username" : "connector@external.com", + "email" : "connector@external.com", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554485976, + "totp" : false, + "credentials" : [ { + "id" : "e370af3a-0e82-4bf3-afec-bab133e5fb70", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554497277, + "secretData" : "{\"value\":\"gOnr545Hgk/ngwkrXeu/RKHseXArWMyFJ4QfNRI6WSY=\",\"salt\":\"c+FwNYtkqaHTMu4AnOa/yw==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Connector", "/Externa org" ] + }, { + "id" : "91143379-ecb7-418d-b597-8b07330ed9bf", + "username" : "connector@filigran.io", + "email" : "connector@filigran.io", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767519159060, + "totp" : false, + "credentials" : [ { + "id" : "46eab7e9-ac5c-4a21-8a17-a18079913723", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767519167037, + "secretData" : "{\"value\":\"syVK4o7itx8hUSnZrOQTW8XmBtJo+cOjqj3iyCalUak=\",\"salt\":\"F9fb7UhdkJrvPv6lhBXXmQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Connector", "/Filigran org" ] + }, { + "id" : "de1a0ff4-7a72-44e5-a852-91a327f4940b", + "username" : "default@external.com", + "email" : "default@external.com", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554454152, + "totp" : false, + "credentials" : [ { + "id" : "1121c9c1-d28d-4391-9f1e-1a849169ce66", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554463181, + "secretData" : "{\"value\":\"i7i6jXb8raMlLaNp1Piqozbu9GIva3nXHZ2GidrdwAU=\",\"salt\":\"gVprwNSpN/jCdARsG2frDw==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ "UPDATE_PASSWORD" ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Default", "/Externa org" ] + }, { + "id" : "2e5b5cf0-042b-4a5a-99a1-cd70d6de7fa3", + "username" : "default@filigran.io", + "email" : "default@filigran.io", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554337650, + "totp" : false, + "credentials" : [ { + "id" : "79425aa2-b60b-4e97-8f50-f084f5c031fa", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554351121, + "secretData" : "{\"value\":\"mNERHi0rMf0Ujr0jMPjAEgEXmEAArN67mXhSXNspGY0=\",\"salt\":\"g93tSxCoCRa/c2PBlVhriQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Default", "/Filigran org" ] + }, { + "id" : "eda57d91-a464-4ca6-828e-c2a7c93fff6d", + "username" : "service-account-openctioid", + "emailVerified" : false, + "enabled" : true, + "createdTimestamp" : 1761217100565, + "totp" : false, + "serviceAccountClientId" : "openctioid", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "clientRoles" : { + "openctioid" : [ "uma_protection" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ] +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/.gitignore b/opencti-platform/opencti-front/.gitignore index 20a9b0f8d022..fb484156bb5f 100644 --- a/opencti-platform/opencti-front/.gitignore +++ b/opencti-platform/opencti-front/.gitignore @@ -1,5 +1,6 @@ .eslintcache config/development.json +.yarnrc.yml # testing /coverage @@ -27,3 +28,4 @@ __generated__ /playwright/.cache/ /.nyc_output/ /tests_e2e/.setup/ +.yarnrc.yml diff --git a/opencti-platform/opencti-front/lang/back/de.json b/opencti-platform/opencti-front/lang/back/de.json index 585214d8101d..66107f52424f 100644 --- a/opencti-platform/opencti-front/lang/back/de.json +++ b/opencti-platform/opencti-front/lang/back/de.json @@ -564,6 +564,7 @@ "Marking type": "Art der Markierung", "Markings": "Markierungen", "Match Pir with source of relationship": "Pirat mit Quelle der Beziehung abgleichen", + "Max concurrent sessions (0 equals no maximum)": "Max. gleichzeitige Sitzungen (0 entspricht keinem Maximum)", "Max Confidence": "Maximale Konfidenz", "Max policy length": "Max policy length", "Max shareable markings": "Maximal teilbare Markierungen", diff --git a/opencti-platform/opencti-front/lang/back/en.json b/opencti-platform/opencti-front/lang/back/en.json index 718d448e7a9e..53969e9c84c0 100644 --- a/opencti-platform/opencti-front/lang/back/en.json +++ b/opencti-platform/opencti-front/lang/back/en.json @@ -564,6 +564,7 @@ "Marking type": "Marking type", "Markings": "Markings", "Match Pir with source of relationship": "Match Pir with source of relationship", + "Max concurrent sessions (0 equals no maximum)": "Max concurrent sessions (0 equals no maximum)", "Max Confidence": "Max Confidence", "Max policy length": "Max policy length", "Max shareable markings": "Max shareable markings", diff --git a/opencti-platform/opencti-front/lang/back/es.json b/opencti-platform/opencti-front/lang/back/es.json index 839aef4d0265..77c4bfc98cee 100644 --- a/opencti-platform/opencti-front/lang/back/es.json +++ b/opencti-platform/opencti-front/lang/back/es.json @@ -564,6 +564,7 @@ "Marking type": "Tipo de marcado", "Markings": "Marcas", "Match Pir with source of relationship": "Emparejar Pir con la fuente de relación", + "Max concurrent sessions (0 equals no maximum)": "Máximo de sesiones simultáneas (0 equivale a ningún máximo)", "Max Confidence": "Confianza máxima", "Max policy length": "Longitud máxima de la política", "Max shareable markings": "Marcas máximas compartibles", diff --git a/opencti-platform/opencti-front/lang/back/fr.json b/opencti-platform/opencti-front/lang/back/fr.json index 510a9b21baba..ae89f7ebdfd1 100644 --- a/opencti-platform/opencti-front/lang/back/fr.json +++ b/opencti-platform/opencti-front/lang/back/fr.json @@ -564,6 +564,7 @@ "Marking type": "Type de marquage", "Markings": "Marques", "Match Pir with source of relationship": "Faire correspondre Pir à la source de la relation", + "Max concurrent sessions (0 equals no maximum)": "Nombre maximal de sessions simultanées (0 signifie qu'il n'y a pas de maximum)", "Max Confidence": "Confiance maximale", "Max policy length": "Longueur maximale de la politique", "Max shareable markings": "Marques maximales partageables", diff --git a/opencti-platform/opencti-front/lang/back/it.json b/opencti-platform/opencti-front/lang/back/it.json index 9223c415f47e..6c8d8b438222 100644 --- a/opencti-platform/opencti-front/lang/back/it.json +++ b/opencti-platform/opencti-front/lang/back/it.json @@ -564,6 +564,7 @@ "Marking type": "Tipo di marcatura", "Markings": "Marcature", "Match Pir with source of relationship": "Abbinare il Pir alla fonte della relazione", + "Max concurrent sessions (0 equals no maximum)": "Sessioni massime contemporanee (0 equivale a nessun massimo)", "Max Confidence": "Massima fiducia", "Max policy length": "Lunghezza massima della politica", "Max shareable markings": "Marcature massime condivisibili", diff --git a/opencti-platform/opencti-front/lang/back/ja.json b/opencti-platform/opencti-front/lang/back/ja.json index f5c153f92acb..2e3b8f269e34 100644 --- a/opencti-platform/opencti-front/lang/back/ja.json +++ b/opencti-platform/opencti-front/lang/back/ja.json @@ -564,6 +564,7 @@ "Marking type": "マーキングタイプ", "Markings": "マーキング", "Match Pir with source of relationship": "海賊と関係元を一致させる", + "Max concurrent sessions (0 equals no maximum)": "最大同時セッション数(0は最大なし)", "Max Confidence": "最大信頼度", "Max policy length": "最大ポリシー長", "Max shareable markings": "共有可能な最大マーキング", diff --git a/opencti-platform/opencti-front/lang/back/ko.json b/opencti-platform/opencti-front/lang/back/ko.json index 1a2d133e9ffe..5db78f3d168c 100644 --- a/opencti-platform/opencti-front/lang/back/ko.json +++ b/opencti-platform/opencti-front/lang/back/ko.json @@ -564,6 +564,7 @@ "Marking type": "마킹 유형", "Markings": "마킹", "Match Pir with source of relationship": "Pir와 관계 소스 일치", + "Max concurrent sessions (0 equals no maximum)": "최대 동시 세션(0은 최대값 없음)", "Max Confidence": "최대 신뢰도", "Max policy length": "최대 정책 길이", "Max shareable markings": "최대 공유 가능 마킹", diff --git a/opencti-platform/opencti-front/lang/back/ru.json b/opencti-platform/opencti-front/lang/back/ru.json index 61003457bb7f..300b5a9d3b4a 100644 --- a/opencti-platform/opencti-front/lang/back/ru.json +++ b/opencti-platform/opencti-front/lang/back/ru.json @@ -564,6 +564,7 @@ "Marking type": "Тип маркировки", "Markings": "Маркировка", "Match Pir with source of relationship": "Сопоставьте Пир с источником отношений", + "Max concurrent sessions (0 equals no maximum)": "Максимальное количество одновременных сеансов (0 означает отсутствие максимума)", "Max Confidence": "Максимальная уверенность", "Max policy length": "Максимальная длина полиса", "Max shareable markings": "Максимально разделяемые метки", diff --git a/opencti-platform/opencti-front/lang/back/zh.json b/opencti-platform/opencti-front/lang/back/zh.json index 73d75fabbeae..1afae949be6a 100644 --- a/opencti-platform/opencti-front/lang/back/zh.json +++ b/opencti-platform/opencti-front/lang/back/zh.json @@ -564,6 +564,7 @@ "Marking type": "标记类型", "Markings": "标记", "Match Pir with source of relationship": "将 Pir 与关系源匹配", + "Max concurrent sessions (0 equals no maximum)": "最大并发会话数(0 表示无最大值)", "Max Confidence": "最大置信度", "Max policy length": "最多长度要求", "Max shareable markings": "最大可共享标记", diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index bc4403d0e1db..d05278cef7c5 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "Agentische KI (Ariane-Assistent)", "Agentic AI capabilities": "Agentische KI-Fähigkeiten", "AI Insights": "AI-Einblicke", + "AI is not enabled": "AI ist nicht aktiviert", "AI Powered": "KI-gesteuert", "AI Summary": "AI Zusammenfassung", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "Beginnend mit OpenCTI 6.0 wird ein maximales Vertrauensniveau für Gruppen und Benutzer hinzugefügt. Damit haben Plattformadministratoren eine bessere Kontrolle über die Auswirkungen importierter Daten und können unerwünschte Datenaktualisierungen verhindern. Bitte lesen Sie {link_blogpost} für weitere Informationen.\n \nAuf dieser Plattform sind \"Max Confidence Level\" einiger Gruppen derzeit undefiniert. Deren Mitglieder können in der Version 6.X der Plattform keine Daten erstellen. Um dies zu verhindern, stellen Sie sicher, dass für alle Gruppen eine maximale Vertrauensstufe festgelegt ist. Mehr dazu in {link_blogpost}. Achten Sie besonders auf Benutzer, die mit Connectors, Feeds und Streams verbunden sind; Sie können die individuelle Vertrauensstufe so einstellen, dass sie die Gruppen-Vertrauensstufe überschreibt, wenn dies erforderlich ist. Treten Sie {link_slack} bei, wenn Sie Ratschläge benötigen.", @@ -221,6 +222,7 @@ "All types of target": "Alle Arten von Ziel", "All years": "Alle Jahre", "Allow modification of sensitive configuration": "Änderung der sensiblen Konfiguration zulassen", + "Allow multiple files": "Mehrere Dateien zulassen", "Allow multiple instances": "Mehrere Instanzen zulassen", "Allow multiple instances of main entity": "Mehrere Instanzen der Hauptentität zulassen", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "ermöglicht die Bereitstellung von Bedrohungsmanagement-Ressourcen wie Feeds, Dashboards, Playbooks usw. mit nur einem Klick", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Eine Aufzählung von Windows-Dienstzuständen", "An enumeration of Windows service types": "Eine Aufzählung von Windows-Diensttypen", "An error occurred while creating the connector": "Beim Erstellen des Verbinders ist ein Fehler aufgetreten", + "An error occurred while importing Synchronizer configuration.": "Beim Importieren der Synchronizer-Konfiguration ist ein Fehler aufgetreten.", + "An error occurred while importing Taxii Feed configuration.": "Beim Importieren der Taxii-Feed-Konfiguration ist ein Fehler aufgetreten.", "An error occurred while retrieving data for this widget:": "Beim Abrufen der Daten für dieses Widget ist ein Fehler aufgetreten:", "An error occurred while updating the connector": "Beim Aktualisieren des Verbinders ist ein Fehler aufgetreten", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "Ein Instanz-Trigger auf eine Entität X meldet die folgenden Ereignisse: Aktualisierung/Löschung von X, Erstellung/Löschung einer Beziehung von/zu X, Erstellung/Löschung einer Entität, die X in ihren Referenzen hat (z.B. enthält X, wird mit X geteilt, wird von X erstellt...), Hinzufügen/Entfernen von X in der Referenz einer Entität.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "Erstelle Observables basierend auf einem Indikator", "Create observables from all indicators in the bundle": "Erstellen von Observablen aus allen Indikatoren im Bundle", "Create observables from this indicator": "Erstellen von Observablen aus diesem Indikator", + "Create OpenCTI Stream": "OpenCTI-Stream erstellen", "Create priority intelligence requirement": "Prioritären Informationsbedarf erstellen", "Create relations in bulk": "Beziehungen in Massen erstellen", "Create relations in bulk for": "Erstellen von Relationen im Bulk für", @@ -1060,6 +1065,7 @@ "Disable forces": "Kräfte deaktivieren", "Disable horizontal tree mode": "Deaktivieren des horizontalen Baummodus", "Disable invert colors": "Farben invertieren deaktivieren", + "Disable on-the-fly entity creation": "On-the-fly-Erstellung von Entitäten deaktivieren", "Disable public dashboard": "Öffentliches Dashboard deaktivieren", "Disable public link": "Öffentlichen Link deaktivieren", "Disable timeout on this page": "Timeout auf dieser Seite deaktivieren", @@ -1918,12 +1924,12 @@ "Heatmap": "Heatmap", "Height": "Größe", "here": "hier", + "Hidden": "Versteckt", "Hidden entity types": "Ausgeblendete Entitätstypen", "Hidden in ": "Versteckt in", "Hidden in groups": "Versteckt in Gruppen", "Hidden in interface": "Versteckt in der Schnittstelle", "Hidden in organizations": "Versteckt in Organisationen", - "Hidden": "Versteckt", "Hide": "Ausblenden", "Hide column": "Spalte ausblenden", "Hide entities in container": "Entitäten im Container ausblenden", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "Importieren eines CSV-Feeds", "Import a CSV mapper": "Einen CSV-Mapper importieren", "Import a JSON mapper": "Importieren eines JSON-Mappers", + "Import a Taxii Feed": "Einen Taxii-Feed importieren", "Import a template": "Eine Vorlage importieren", "Import a theme": "Importieren eines Themes", "Import a widget": "Ein Widget importieren", "Import a Widget": "Ein Widget importieren", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "Importieren Sie alle Daten in einen neuen Entwurf oder eine Analystenwerkbank, um die Daten vor der Aufnahme zu validieren. Beachten Sie, dass die Erstellung einer Workbench nicht möglich ist, wenn mehrere Dateien ausgewählt sind.", + "Import an OpenCTI Stream": "Importieren eines OpenCTI-Streams", "Import dashboard": "Dashboard importieren", "Import data": "Daten importieren", "Import files": "Dateien importieren", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "Entspricht allen Indikator-Hauptbeobachtungstypen, wenn keiner aufgeführt ist.", "Matrix in line view": "Matrix in line Ansicht", "Matrix view": "Matrixansicht", + "Max concurrent sessions (0 equals no maximum)": "Max. gleichzeitige Sitzungen (0 entspricht keinem Maximum)", "Max Confidence": "Maximalvertrauen", "Max Confidence is overridden for some entity types:": "Max Confidence wird für einige Entitätstypen außer Kraft gesetzt:", "Max Confidence Level": "Max. Konfidenzniveau", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "Am wichtigsten - 100", "Most targeted victims (Last 3 months)": "Die meisten angegriffenen Opfer (letzte 3 Monate)", "Most used filters": "Meist genutzte Filter", + "Move down": "Abwärts bewegen", + "Move up": "Nach oben verschieben", "Multi-Select": "Mehrfachauswahl", "Multiple": "Mehrere", "Multiple (limited to 5)": "Mehrfach (begrenzt auf 5)", @@ -3267,7 +3278,6 @@ "Remove from the container": "Aus dem Container entfernen", "Remove from this entity": "Aus dieser Entität entfernen", "Remove from this object": "Aus diesem Objekt entfernen", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "Der Vorgang \"Entfernen\" wird nur auf Feldwerte angewandt, die im Kontext dieses Playbooks hinzugefügt wurden, z. B. durch Anreicherung oder andere Wissensmanipulationen, nicht aber, wenn bereits Werte in die Plattform geschrieben wurden.", "Remove selected items": "Ausgewählte Elemente entfernen", "Remove this reaction point": "Entfernen Sie diesen Reaktionspunkt", "REMOVE_AUTH_MEMBERS": "REMOVE_AUTH_MEMBERS", @@ -3276,8 +3286,6 @@ "Renew": "Erneuern", "REPLACE": "ERSETZEN", "Replace": "Ersetzen Sie", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "Die Operation \"Ersetzen\" ersetzt den Autor effektiv, wenn die Vertrauensstufe der Entität mit dem neuen Autor höher ist als die der Entität mit dem alten Autor.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "Die Operation \"Ersetzen\" ersetzt die im Kontext dieses Playbooks hinzugefügten Feldwerte, z. B. durch Anreicherung oder andere Wissensmanipulationen, aber nur dann, wenn die Werte bereits in die Plattform geschrieben wurden.", "Report": "Bericht", "Report actions": "Aktionen melden", "Report Confidence (RC)": "Berichtskonfidenz (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Textarea", - "Thank you!": "Vielen Dank!", "Thank you for reaching out, we'll get back to you shortly.": "Vielen Dank für Ihre Anfrage, wir werden Sie in Kürze kontaktieren.", + "Thank you!": "Vielen Dank!", "The alias has been added": "Der Alias wurde hinzugefügt", "The alias has been removed": "Der Alias wurde entfernt", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "Die Anzahl der Ergebnisse kann je nach Daten/Beziehungen unterschiedlich sein, da jedes Auftreten einer abgeleiteten Beziehung für dieses Widget gezählt wird.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "Sie müssen die EE-Lizenz aktivieren, um diese Funktion nutzen zu können", "You need to validate your two-factor authentication. Please type the code generated in your application": "Sie müssen Ihre Zwei-Faktor-Authentifizierung validieren. Bitte geben Sie den in Ihrer Anwendung generierten Code ein.", "You see only marking definitions that can be shared (defined by the admin)": "Sie sehen nur Markierungsdefinitionen, die freigegeben werden können (vom Administrator definiert)", + "You should activate EE to use this feature": "Sie sollten EE aktivieren, um diese Funktion zu nutzen", "You should provide a variable name": "Sie sollten einen Variablennamen angeben", "You will be able to revert this change if needed. ": "Sie können diese Änderung bei Bedarf rückgängig machen.", "You will be automatically logged out at end of the timer.": "Sie werden nach Ablauf der Zeit automatisch abgemeldet.", @@ -4541,4 +4550,4 @@ "Zoom": "Vergrößern", "Zoom in": "Vergrößern", "Zoom out": "Verkleinern" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index e44082d12643..7ac8de8201be 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "Agentic AI (Ariane Assistant)", "Agentic AI capabilities": "Agentic AI capabilities", "AI Insights": "AI Insights", + "AI is not enabled": "AI is not enabled", "AI Powered": "AI Powered", "AI Summary": "AI Summary", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "Starting with OpenCTI 6.0, a Max Confidence Level will be added to groups and users. It will provide platform administrators with more control on the impact of imported data and prevent any unwanted update on data. Please read {link_blogpost} for more information.\n \nOn this platform, \"Max Confidence Level\" of some groups are currently undefined. Their members will not be able to create any data in version 6.X of the platform. In order to prevent that, make sure that all groups have a Max Confidence Level set. More info in {link_blogpost}. Be particularly mindful of users associated to Connectors, Feeds and Streams; you can set individual confidence level to override group confidence level if needed. Join {link_slack} if you need advices.", @@ -221,6 +222,7 @@ "All types of target": "All types of target", "All years": "All years", "Allow modification of sensitive configuration": "Allow modification of sensitive configuration", + "Allow multiple files": "Allow multiple files", "Allow multiple instances": "Allow multiple instances", "Allow multiple instances of main entity": "Allow multiple instances of main entity", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "An enumeration of Windows service statuses", "An enumeration of Windows service types": "An enumeration of Windows service types", "An error occurred while creating the connector": "An error occurred while creating the connector", + "An error occurred while importing Synchronizer configuration.": "An error occurred while importing Synchronizer configuration.", + "An error occurred while importing Taxii Feed configuration.": "An error occurred while importing Taxii Feed configuration.", "An error occurred while retrieving data for this widget:": "An error occurred while retrieving data for this widget:", "An error occurred while updating the connector": "An error occurred while updating the connector", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "Create observables based on an indicator", "Create observables from all indicators in the bundle": "Create observables from all indicators in the bundle", "Create observables from this indicator": "Create observables from this indicator", + "Create OpenCTI Stream": "Create OpenCTI Stream", "Create priority intelligence requirement": "Create priority intelligence requirement", "Create relations in bulk": "Create relations in bulk", "Create relations in bulk for": "Create relations in bulk for", @@ -1060,6 +1065,7 @@ "Disable forces": "Disable forces", "Disable horizontal tree mode": "Disable horizontal tree mode", "Disable invert colors": "Disable invert colors", + "Disable on-the-fly entity creation": "Disable on-the-fly entity creation", "Disable public dashboard": "Disable public dashboard", "Disable public link": "Disable public link", "Disable timeout on this page": "Disable timeout on this page", @@ -1918,12 +1924,12 @@ "Heatmap": "Heatmap", "Height": "Height", "here": "here", + "Hidden": "Hidden", "Hidden entity types": "Hidden entity types", "Hidden in ": "Hidden in ", "Hidden in groups": "Hidden in groups", "Hidden in interface": "Hidden in interface", "Hidden in organizations": "Hidden in organizations", - "Hidden": "Hidden", "Hide": "Hide", "Hide column": "Hide column", "Hide entities in container": "Hide entities in container", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "Import a CSV feed", "Import a CSV mapper": "Import a CSV mapper", "Import a JSON mapper": "Import a JSON mapper", + "Import a Taxii Feed": "Import a Taxii Feed", "Import a template": "Import a template", "Import a theme": "Import a theme", "Import a widget": "Import a widget", "Import a Widget": "Import a Widget", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.", + "Import an OpenCTI Stream": "Import an OpenCTI Stream", "Import dashboard": "Import dashboard", "Import data": "Import data", "Import files": "Import files", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "Matches all indicator main observable types if none is listed.", "Matrix in line view": "Matrix in line view", "Matrix view": "Matrix view", + "Max concurrent sessions (0 equals no maximum)": "Max concurrent sessions (0 equals no maximum)", "Max Confidence": "Max Confidence", "Max Confidence is overridden for some entity types:": "Max Confidence is overridden for some entity types:", "Max Confidence Level": "Max Confidence Level", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "Most relevant - 100", "Most targeted victims (Last 3 months)": "Most targeted victims (Last 3 months)", "Most used filters": "Most used filters", + "Move down": "Move down", + "Move up": "Move up", "Multi-Select": "Multi-Select", "Multiple": "Multiple", "Multiple (limited to 5)": "Multiple (limited to 5)", @@ -3267,7 +3278,6 @@ "Remove from the container": "Remove from the container", "Remove from this entity": "Remove from this entity", "Remove from this object": "Remove from this object", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.", "Remove selected items": "Remove selected items", "Remove this reaction point": "Remove this reaction point", "REMOVE_AUTH_MEMBERS": "REMOVE_AUTH_MEMBERS", @@ -3276,8 +3286,6 @@ "Renew": "Renew", "REPLACE": "REPLACE", "Replace": "Replace", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.", "Report": "Report", "Report actions": "Report actions", "Report Confidence (RC)": "Report Confidence (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Textarea", - "Thank you!": "Thank you!", "Thank you for reaching out, we'll get back to you shortly.": "Thank you for reaching out, we'll get back to you shortly.", + "Thank you!": "Thank you!", "The alias has been added": "The alias has been added", "The alias has been removed": "The alias has been removed", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "You need to enable EE License to use this feature", "You need to validate your two-factor authentication. Please type the code generated in your application": "You need to validate your two-factor authentication. Please type the code generated in your application.", "You see only marking definitions that can be shared (defined by the admin)": "You see only marking definitions that can be shared (defined by the admin)", + "You should activate EE to use this feature": "You should activate EE to use this feature", "You should provide a variable name": "You should provide a variable name", "You will be able to revert this change if needed. ": "You will be able to revert this change if needed. ", "You will be automatically logged out at end of the timer.": "You will be automatically logged out at end of the timer.", @@ -4541,4 +4550,4 @@ "Zoom": "Zoom", "Zoom in": "Zoom in", "Zoom out": "Zoom out" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index 54b746bdf0ce..57d2083488b9 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "IA Agenética (Asistente Ariane)", "Agentic AI capabilities": "Capacidades de IA agéntica", "AI Insights": "AI Insights", + "AI is not enabled": "La IA no está activada", "AI Powered": "AI Powered", "AI Summary": "Resumen AI", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "A partir de OpenCTI 6.0, se añadirá un nivel de confianza máximo a los grupos y usuarios. Proporcionará a los administradores de la plataforma un mayor control sobre el impacto de los datos importados y evitará cualquier actualización no deseada de los datos. Lea {link_blogpost} para obtener más información.\n \nEn esta plataforma, \"el nivel máximo de confianza\" de algunos grupos no están definidos actualmente. Sus miembros no podrán crear ningún dato en la versión 6.X de la plataforma. Para evitarlo, asegúrate de que todos los grupos tienen un nivel de confianza máximo definido. Más información en {link_blogpost}. Ten especial cuidado con los usuarios asociados a Conectores, Feeds y Streams; puedes establecer el nivel de confianza individual para anular el nivel de confianza de grupo si es necesario. Únete a {link_slack} si necesitas consejos.", @@ -221,6 +222,7 @@ "All types of target": "Todo los tipos de objetivo", "All years": "Todos los años", "Allow modification of sensitive configuration": "Permitir la modificación de la configuración sensible", + "Allow multiple files": "Permitir varios archivos", "Allow multiple instances": "Permitir múltiples instancias", "Allow multiple instances of main entity": "Permitir múltiples instancias de la entidad principal", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "permiten desplegar en un clic recursos de gestión de amenazas como feeds, cuadros de mando, playbooks, etc.", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Una enumeración de estados de servicio de Windows", "An enumeration of Windows service types": "Una enumeración de tipos de servicio Windows", "An error occurred while creating the connector": "Se ha producido un error al crear el conector", + "An error occurred while importing Synchronizer configuration.": "Se ha producido un error al importar la configuración del sincronizador.", + "An error occurred while importing Taxii Feed configuration.": "Se ha producido un error al importar la configuración de Taxii Feed.", "An error occurred while retrieving data for this widget:": "Se ha producido un error al recuperar los datos de este widget:", "An error occurred while updating the connector": "Se ha producido un error al actualizar el conector", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "Un desencadenador de instancia en una entidad X notifica los siguientes eventos: actualización/eliminación de X, creación/eliminación de una relación de/hacia X, creación/eliminación de una entidad que tiene X en sus referencias (por ejemplo, contiene X, se comparte con X, es creada por X...), agrega/elimina X en la referencia de una entidad.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "Crear observables basados en un indicador", "Create observables from all indicators in the bundle": "Crear observables a partir de todos los indicadores del paquete", "Create observables from this indicator": "Crear observables a partir de este indicador", + "Create OpenCTI Stream": "Crear flujo OpenCTI", "Create priority intelligence requirement": "Crear una necesidad de información prioritaria", "Create relations in bulk": "Crear relaciones en bloque", "Create relations in bulk for": "Crear relaciones en bloque para", @@ -1060,6 +1065,7 @@ "Disable forces": "Desactivar las fuerzas", "Disable horizontal tree mode": "Desactivar el modo de árbol horizontal", "Disable invert colors": "Desactivar invertir colores", + "Disable on-the-fly entity creation": "Desactivar la creación de entidades sobre la marcha", "Disable public dashboard": "Desactivar panel público", "Disable public link": "Desactivar enlace público", "Disable timeout on this page": "Deshabilitar el tiempo de espera en esta página", @@ -1918,12 +1924,12 @@ "Heatmap": "Mapa de calor", "Height": "Altura", "here": "aquí", + "Hidden": "¿Escondido", "Hidden entity types": "Tipos de entidad ocultos", "Hidden in ": "Oculto en ", "Hidden in groups": "Oculto en grupos", "Hidden in interface": "Oculto en la interfaz", "Hidden in organizations": "Oculto en las organizaciones", - "Hidden": "¿Escondido", "Hide": "Ocultar", "Hide column": "Ocultar columna", "Hide entities in container": "Ocultar entidades en el contenedor", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "Importar un feed CSV", "Import a CSV mapper": "Importar un mapeador CSV", "Import a JSON mapper": "Importar un mapeador JSON", + "Import a Taxii Feed": "Importar un feed de Taxii", "Import a template": "Importar una plantilla", "Import a theme": "Importar un tema", "Import a widget": "Importar un widget", "Import a Widget": "Importar un widget", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "Importe todos los datos a un nuevo borrador o a un banco de trabajo de analista, para validar los datos antes de su ingestión. Tenga en cuenta que la creación de un banco de trabajo no es posible cuando se seleccionan varios archivos.", + "Import an OpenCTI Stream": "Importar un flujo OpenCTI", "Import dashboard": "Importar un cuadro de mando personalizado", "Import data": "Importar datos", "Import files": "Importar archivos", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "Coincide con todos los tipos observables principales del indicador si no se enumera ninguno.", "Matrix in line view": "Vista en línea de la matriz", "Matrix view": "Vista en matriz", + "Max concurrent sessions (0 equals no maximum)": "Máximo de sesiones simultáneas (0 equivale a ningún máximo)", "Max Confidence": "Max Confianza", "Max Confidence is overridden for some entity types:": "La confianza máxima se anula para algunos tipos de entidad:", "Max Confidence Level": "Nivel de confianza máximo", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "Más relevante - 100", "Most targeted victims (Last 3 months)": "Víctimas más atacadas (Últimos 3 meses)", "Most used filters": "Filtros más utilizados", + "Move down": "Bajar", + "Move up": "Subir", "Multi-Select": "Selección múltiple", "Multiple": "Múltiple", "Multiple (limited to 5)": "Múltiple (limitado a 5)", @@ -3267,7 +3278,6 @@ "Remove from the container": "Borrar del contenedor", "Remove from this entity": "Quitar esta entidad", "Remove from this object": "Quitar este objeto", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "La operación de eliminación sólo se aplicará a los valores de campo añadidos en el contexto de este libro de jugadas, como el enriquecimiento u otras manipulaciones del conocimiento, pero no si los valores ya están escritos en la plataforma.", "Remove selected items": "Quitar los elementos seleccionados", "Remove this reaction point": "Eliminar este punto de reacción", "REMOVE_AUTH_MEMBERS": "REMOVE_AUTH_MEMBERS", @@ -3276,8 +3286,6 @@ "Renew": "Renovar", "REPLACE": "REEMPLAZAR", "Replace": "Reemplazarr", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "La operación Reemplazar sustituirá efectivamente al autor si el nivel de confianza de la entidad con el nuevo autor es superior al de la entidad con el antiguo autor.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "La operación Reemplazar reemplazará efectivamente los valores de este campo añadidos en el contexto de este libro de jugadas como el enriquecimiento u otras manipulaciones del conocimiento pero sólo los añadirá si los valores ya están escritos en la plataforma.", "Report": "Informe", "Report actions": "Informar de las acciones", "Report Confidence (RC)": "Informe de confianza (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Área de texto", - "Thank you!": "¡Gracias!", "Thank you for reaching out, we'll get back to you shortly.": "¡Gracias por contactarnos, te responderemos lo antes posible!", + "Thank you!": "¡Gracias!", "The alias has been added": "El alias has sido añadido", "The alias has been removed": "El alias ha sido eliminado", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "La cantidad de resultados puede variar en función de los datos/relaciones, ya que cada aparición de una relación inferida se cuenta para este widget.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "Debe activar la licencia EE para utilizar esta función", "You need to validate your two-factor authentication. Please type the code generated in your application": "Debe validar su autenticación de dos factores. Por favor, escriba el código generado en su aplicación.", "You see only marking definitions that can be shared (defined by the admin)": "Sólo se ven las definiciones de marcado que se pueden compartir (definidas por el administrador)", + "You should activate EE to use this feature": "Debe activar EE para utilizar esta función", "You should provide a variable name": "Debes proporcionar un nombre de variable", "You will be able to revert this change if needed. ": "Podrás revertir este cambio si es necesario.", "You will be automatically logged out at end of the timer.": "Se cerrará la sesión automáticamente al final del temporizador.", @@ -4541,4 +4550,4 @@ "Zoom": "Zoom", "Zoom in": "Ampliar", "Zoom out": "Alejar" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 6cfd0ec4e59e..773d01c440ca 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "IA agentique (assistant Ariane)", "Agentic AI capabilities": "Capacités de l'IA agentique", "AI Insights": "Aperçu par AI", + "AI is not enabled": "L'IA n'est pas activée", "AI Powered": "AI Powered", "AI Summary": "Résumé AI", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "À partir d'OpenCTI 6.0, un niveau de confiance maximal sera ajouté aux groupes et aux utilisateurs. Cela permettra aux administrateurs de la plateforme de mieux contrôler l'impact des données importées et d'éviter toute mise à jour indésirable des données. Veuillez lire {link_blogpost} pour plus d'informations.\n \nSur cette plateforme, le niveau de confiance maximum de certains groupes est actuellement indéfini. Leurs membres ne pourront pas créer de données dans la version 6.X de la plateforme. Pour éviter cela, assurez-vous que tous les groupes ont un niveau de confiance maximum défini. Plus d'informations dans {link_blogpost}. Soyez particulièrement attentif aux utilisateurs associés aux connecteurs, aux streams et aux feeds ; vous pouvez définir un niveau de confiance individuel pour remplacer le niveau de confiance du groupe si nécessaire. Rejoignez {link_slack} si vous avez besoin de conseils.", @@ -221,6 +222,7 @@ "All types of target": "Tous les types de cible", "All years": "Toutes les années", "Allow modification of sensitive configuration": "Permettre la modification d'une configuration sensible", + "Allow multiple files": "Autoriser les fichiers multiples", "Allow multiple instances": "Autoriser les instances multiples", "Allow multiple instances of main entity": "Autoriser plusieurs instances de l'entité principale", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "permet de déployer en un clic des ressources de gestion des menaces telles que des flux, des tableaux de bord, des playbooks, etc.", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Une énumération de statuts de services Windows", "An enumeration of Windows service types": "Une énumération des types de services Windows", "An error occurred while creating the connector": "Une erreur s'est produite lors de la création du connecteur", + "An error occurred while importing Synchronizer configuration.": "Une erreur s'est produite lors de l'importation de la configuration du Synchronizer.", + "An error occurred while importing Taxii Feed configuration.": "Une erreur s'est produite lors de l'importation de la configuration de Taxii Feed.", "An error occurred while retrieving data for this widget:": "Une erreur s'est produite lors de la récupération des données pour ce widget :", "An error occurred while updating the connector": "Une erreur s'est produite lors de la mise à jour du connecteur", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "Un déclencheur d’instance sur une entité X notifie les événements suivants : mise à jour/suppression de X, création/suppression d’une relation de/vers X, création/suppression d’une entité qui a X dans ses refs (par exemple contient X, est partagée avec X, est créée par X...), ajout/suppression de X dans la ref d’une entité.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "Créer des observables basés sur un indicateur", "Create observables from all indicators in the bundle": "Créer des observables à partir de tous les indicateurs du bundle", "Create observables from this indicator": "Créer des observables à partir de cet indicateur", + "Create OpenCTI Stream": "Créer un flux OpenCTI", "Create priority intelligence requirement": "Créer des besoins prioritaires en matière de renseignement", "Create relations in bulk": "Créer des relations en masse", "Create relations in bulk for": "Créer des relations en masse pour", @@ -1060,6 +1065,7 @@ "Disable forces": "Désactiver les forces", "Disable horizontal tree mode": "Désactiver le mode arbre horizontal", "Disable invert colors": "Désactiver l'inversion des couleurs", + "Disable on-the-fly entity creation": "Désactiver la création d'entités à la volée", "Disable public dashboard": "Désactiver le tableau de bord public", "Disable public link": "Désactiver le lien public", "Disable timeout on this page": "Désactiver le délai d'attente sur cette page", @@ -1918,12 +1924,12 @@ "Heatmap": "Carte de chaleur", "Height": "Hauteur", "here": "ici", + "Hidden": "Caché", "Hidden entity types": "Types d'entité cachés", "Hidden in ": "Caché dans ", "Hidden in groups": "Caché dans les groupes", "Hidden in interface": "Caché dans l'interface", "Hidden in organizations": "Caché dans les organisations", - "Hidden": "Caché", "Hide": "Cacher", "Hide column": "Masquer la colonne", "Hide entities in container": "Cacher les entités dans le conteneur", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "Importer un flux CSV", "Import a CSV mapper": "Importer un mappeur CSV", "Import a JSON mapper": "Importer un mappeur JSON", + "Import a Taxii Feed": "Importer un flux Taxii", "Import a template": "Importer un modèle", "Import a theme": "Importer un thème", "Import a widget": "Importer un widget", "Import a Widget": "Importer un widget", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "Importer toutes les données dans un nouveau brouillon ou dans un espace de travail analyste, afin de valider les données avant l'ingestion. Notez que la création d'un atelier n'est pas possible lorsque plusieurs fichiers sont sélectionnés.", + "Import an OpenCTI Stream": "Importer un flux OpenCTI", "Import dashboard": "Importer un tableau de bord personnalisé", "Import data": "Importer des données", "Import files": "Importer des fichiers", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "Correspond à tous les types d'observables principaux des indicateurs si aucun n'est listé.", "Matrix in line view": "Vue en ligne de la matrice", "Matrix view": "Vue matricielle", + "Max concurrent sessions (0 equals no maximum)": "Nombre maximal de sessions simultanées (0 signifie qu'il n'y a pas de maximum)", "Max Confidence": "Confiance max", "Max Confidence is overridden for some entity types:": "La confiance max est surchargée pour certains types d'entités :", "Max Confidence Level": "Niveau de confiance maximal", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "Plus pertinent - 100", "Most targeted victims (Last 3 months)": "Victimes les plus ciblées (3 derniers mois)", "Most used filters": "Filtres les plus utilisés", + "Move down": "Déplacer vers le bas", + "Move up": "Déplacer vers le haut", "Multi-Select": "Multi-sélection", "Multiple": "Multiple", "Multiple (limited to 5)": "Multiple (limité à 5)", @@ -3267,7 +3278,6 @@ "Remove from the container": "Retirer de ce conteneur", "Remove from this entity": "Retirer de cette entité", "Remove from this object": "Retirer de cet objet", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "L'opération de suppression ne s'appliquera qu'aux valeurs de champ ajoutées dans le contexte de ce playbook, comme l'enrichissement ou d'autres manipulations de connaissances, mais pas si les valeurs sont déjà écrites dans la plateforme.", "Remove selected items": "Supprimer les éléments sélectionnés", "Remove this reaction point": "Supprimer ce point de réaction", "REMOVE_AUTH_MEMBERS": "RETIRER_AUTH_MEMBERS", @@ -3276,8 +3286,6 @@ "Renew": "Renouveler", "REPLACE": "REMPLACER", "Replace": "Remplacer", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "L'opération de remplacement remplacera effectivement l'auteur si le niveau de confiance de l'entité avec le nouvel auteur est supérieur à celui de l'entité avec l'ancien auteur.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "L'opération de remplacement, ne remplacera effectivement que les valeurs de ce champ ajoutées dans le contexte de ce playbook tel que l'enrichissement ou d'autres manipulations de connaissances, mais elle ne les ajoutera que si les valeurs sont déjà écrites dans la plateforme.", "Report": "Report", "Report actions": "Rapport d'actions", "Report Confidence (RC)": "Rapport de confiance (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Zone de texte", - "Thank you!": "Merci!", "Thank you for reaching out, we'll get back to you shortly.": "Merci d'avoir contacté notre équipe, nous vous répondrons dans les plus brefs délais.", + "Thank you!": "Merci!", "The alias has been added": "L'alias a bien été ajouté", "The alias has been removed": "L'alias a été supprimé", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "Le nombre de résultats peut varier en fonction des données/relations, car chaque occurrence d'une relation déduite est comptée pour ce widget.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "Vous devez activer la licence EE pour utiliser cette fonction", "You need to validate your two-factor authentication. Please type the code generated in your application": "Vous devez valider votre authentification à deux facteurs. Veuillez saisir le code généré dans votre application.", "You see only marking definitions that can be shared (defined by the admin)": "Vous ne voyez que les définitions de marquage qui peuvent être partagées (définies par l'administrateur)", + "You should activate EE to use this feature": "Vous devez activer l'EE pour utiliser cette fonction", "You should provide a variable name": "Vous devez fournir un nom de variable", "You will be able to revert this change if needed. ": "Vous pourrez revenir sur cette modification si nécessaire.", "You will be automatically logged out at end of the timer.": "Vous serez automatiquement déconnecté à la fin du décompte.", @@ -4541,4 +4550,4 @@ "Zoom": "Zoom", "Zoom in": "Zoomer", "Zoom out": "Zoom arrière" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/it.json b/opencti-platform/opencti-front/lang/front/it.json index 3c9eaf3d8811..22b9294eeeb8 100644 --- a/opencti-platform/opencti-front/lang/front/it.json +++ b/opencti-platform/opencti-front/lang/front/it.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "IA agenziale (Assistente Arianna)", "Agentic AI capabilities": "Funzionalità di IA agenziale", "AI Insights": "AI Insights", + "AI is not enabled": "L'intelligenza artificiale non è abilitata", "AI Powered": "AI Powered", "AI Summary": "AI Summary", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "A partire da OpenCTI 6.0, verrà aggiunto un livello di fiducia massimo ai gruppi e agli utenti. Questo permetterà agli amministratori della piattaforma di avere un maggiore controllo sull'impatto dei dati importati e di prevenire aggiornamenti indesiderati sui dati. Per maggiori informazioni, consultare {link_blogpost}.\n\nSu questa piattaforma, il \"Livello di Fiducia Massimo\" di alcuni gruppi non è attualmente definito. I loro membri non potranno creare dati nella versione 6.X della piattaforma. Per evitare questo problema, assicurati che tutti i gruppi abbiano un livello di fiducia massimo impostato. Più informazioni su {link_blogpost}. Presta particolare attenzione agli utenti associati a Connettori, Feed e Stream; se necessario, puoi impostare un livello di fiducia individuale per sovrascrivere quello del gruppo. Unisciti a {link_slack} se hai bisogno di consigli.", @@ -221,6 +222,7 @@ "All types of target": "Tutti i tipi di target", "All years": "Tutti gli anni", "Allow modification of sensitive configuration": "Consenti la modifica della configurazione sensibile", + "Allow multiple files": "Consentire più file", "Allow multiple instances": "Consentire istanze multiple", "Allow multiple instances of main entity": "Consentire istanze multiple dell'entità principale", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "consentono di distribuire in un solo clic risorse di gestione delle minacce come feed, dashboard, playbook, ecc.", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Un'enumerazione di stati del servizio Windows", "An enumeration of Windows service types": "Un'enumerazione di tipi di servizio Windows", "An error occurred while creating the connector": "Si è verificato un errore durante la creazione del connettore", + "An error occurred while importing Synchronizer configuration.": "Si è verificato un errore durante l'importazione della configurazione di Synchronizer.", + "An error occurred while importing Taxii Feed configuration.": "Si è verificato un errore durante l'importazione della configurazione di Taxii Feed.", "An error occurred while retrieving data for this widget:": "Si è verificato un errore durante il recupero dei dati per questo widget:", "An error occurred while updating the connector": "Si è verificato un errore durante l'aggiornamento del connettore", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "Un trigger di istanza su un'entità X notifica gli eventi seguenti: aggiornamento/eliminazione di X, creazione/eliminazione di una relazione da/per X, creazione/eliminazione di un'entità che ha X nei suoi riferimenti (ad esempio contiene X, è condiviso con X, è creato da X...), aggiunta/rimozione di X nei riferimenti di un'entità.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "Creare osservabili basati su un indicatore", "Create observables from all indicators in the bundle": "Creare osservabili da tutti gli indicatori del bundle", "Create observables from this indicator": "Crea osservabili da questo indicatore", + "Create OpenCTI Stream": "Creare il flusso OpenCTI", "Create priority intelligence requirement": "Creare un requisito di intelligence prioritario", "Create relations in bulk": "Crea relazioni in bulk", "Create relations in bulk for": "Crea relazioni in bulk per", @@ -1060,6 +1065,7 @@ "Disable forces": "Disabilita le forze", "Disable horizontal tree mode": "Disabilita la modalità albero orizzontale", "Disable invert colors": "Disabilita l'inversione dei colori", + "Disable on-the-fly entity creation": "Disabilitare la creazione di entità al volo", "Disable public dashboard": "Disabilita la dashboard pubblica", "Disable public link": "Disabilita il link pubblico", "Disable timeout on this page": "Disabilita il timeout su questa pagina", @@ -1918,12 +1924,12 @@ "Heatmap": "Mappa di calore", "Height": "Altezza", "here": "qui", + "Hidden": "Nascosto", "Hidden entity types": "Tipi di entità nascosti", "Hidden in ": "Nascosto in ", "Hidden in groups": "Nascosto nei gruppi", "Hidden in interface": "Nascosto nell'interfaccia", "Hidden in organizations": "Nascosto nelle organizzazioni", - "Hidden": "Nascosto", "Hide": "Nascondi", "Hide column": "Nascondi colonna", "Hide entities in container": "Nascondi entità nel contenitore", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "Importare un feed CSV", "Import a CSV mapper": "Importare un mappatore CSV", "Import a JSON mapper": "Importare un mappatore JSON", + "Import a Taxii Feed": "Importare un feed Taxii", "Import a template": "Importa un template", "Import a theme": "Importazione di un tema", "Import a widget": "Importa un widget", "Import a Widget": "Importa un Widget", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "Importare tutti i dati in una nuova bozza o in un workbench di analisi, per convalidare i dati prima dell'ingestione. Si noti che la creazione di un workbench non è possibile quando sono selezionati più file.", + "Import an OpenCTI Stream": "Importare un flusso OpenCTI", "Import dashboard": "Importa dashboard", "Import data": "Importa dati", "Import files": "Importazione di file", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "Corrisponde a tutti i tipi di osservabili principali degli indicatori se nessuno è elencato.", "Matrix in line view": "Matrice in visualizzazione lineare", "Matrix view": "Visualizzazione matrice", + "Max concurrent sessions (0 equals no maximum)": "Sessioni massime contemporanee (0 equivale a nessun massimo)", "Max Confidence": "Massima fiducia", "Max Confidence is overridden for some entity types:": "Massima fiducia è sovrascritto per alcuni tipi di entità:", "Max Confidence Level": "Massimo livello di fiducia", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "Più rilevante - 100", "Most targeted victims (Last 3 months)": "Vittime più bersagliate (Ultimi 3 mesi)", "Most used filters": "Filtri più utilizzati", + "Move down": "Scendere", + "Move up": "Sposta in alto", "Multi-Select": "Selezione multipla", "Multiple": "Multiplo", "Multiple (limited to 5)": "Multiplo (limitato a 5)", @@ -3267,7 +3278,6 @@ "Remove from the container": "Rimuovi dal contenitore", "Remove from this entity": "Rimuovi da questa entità", "Remove from this object": "Rimuovi da questo oggetto", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.", "Remove selected items": "Rimuovi gli elementi selezionati", "Remove this reaction point": "Rimuovi questo punto di reazione", "REMOVE_AUTH_MEMBERS": "RIMUOVI_AUTH_MEMBRI", @@ -3276,8 +3286,6 @@ "Renew": "Rinnova", "REPLACE": "SOSTITUIRE", "Replace": "Sostituisci", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "L'operazione Replace sostituisce effettivamente l'autore se il livello di confidenza dell'entità con il nuovo autore è superiore a quello dell'entità con il vecchio autore.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "Rimpiazzare l'operazione sostituirà effettivamente i valori di questo campo aggiunti nel contesto di questo playbook, come arricchimento o altre manipolazioni della conoscenza, ma li aggiungerà solo se i valori sono già scritti nella piattaforma.", "Report": "Report", "Report actions": "Azioni del report", "Report Confidence (RC)": "Fiducia nel rapporto (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Area di testo", - "Thank you!": "Grazie!", "Thank you for reaching out, we'll get back to you shortly.": "Grazie per averci contattato, ti risponderemo a breve.", + "Thank you!": "Grazie!", "The alias has been added": "L'alias è stato aggiunto", "The alias has been removed": "L'alias è stato rimosso", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "La quantità di risultati può variare in base ai dati/alle relazioni, poiché ogni occorrenza di una relazione dedotta viene contata per questo widget.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "È necessario abilitare la licenza EE per utilizzare questa funzione", "You need to validate your two-factor authentication. Please type the code generated in your application": "Devi convalidare la tua autenticazione a due fattori. Inserisci il codice generato nella tua applicazione.", "You see only marking definitions that can be shared (defined by the admin)": "Vedi solo le definizioni di marcatura che possono essere condivise (definite dall'amministratore)", + "You should activate EE to use this feature": "È necessario attivare EE per utilizzare questa funzione", "You should provide a variable name": "Dovresti fornire un nome per la variabile", "You will be able to revert this change if needed. ": "Sarà possibile ripristinare questa modifica se necessario.", "You will be automatically logged out at end of the timer.": "Verrai disconnesso automaticamente alla fine del timer.", @@ -4541,4 +4550,4 @@ "Zoom": "Zoom", "Zoom in": "Ingrandisci", "Zoom out": "Riduci" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index d7499f0484e9..9df761e100e7 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "エージェント型AI(アリアン・アシスタント)", "Agentic AI capabilities": "エージェントAIの能力", "AI Insights": "AIインサイト", + "AI is not enabled": "AIは有効ではない", "AI Powered": "AI搭載", "AI Summary": "AIサマリー", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "OpenCTI 6.0 から、グループとユーザーに最大信頼度が追加されます。これにより、プラットフォーム管理者はインポートされたデータの影響をよりコントロールできるようになり、データの不要な更新を防ぐことができます。詳しくは{link_blogpost}をお読みください。\n \nこのプラットフォームでは、いくつかのグループの最大信頼度が現在未定義です。そのグループのメンバーはバージョン6.Xではデータを作成することができません。これを防ぐには、すべてのグループに最大信頼度が設定されていることを確認してください。詳細は{link_blogpost}を参照してください。コネクター、フィード、ストリームに関連するユーザーには特に注意してください。必要であれば、グループの信頼度よりも個人の信頼度を優先して設定できます。アドバイスが必要な場合は{link_slack}に参加してください。", @@ -221,6 +222,7 @@ "All types of target": "あらゆる種類のターゲット", "All years": "すべての年", "Allow modification of sensitive configuration": "機密設定の変更を許可する", + "Allow multiple files": "複数のファイルを許可する", "Allow multiple instances": "複数のインスタンスを許可する", "Allow multiple instances of main entity": "主エンティティの複数インスタンスを許可する", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "フィード、ダッシュボード、プレイブックなどの脅威管理リソースをワンクリックで展開できる。", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Windowsサービスステータスの列挙", "An enumeration of Windows service types": "Windows サービスタイプの列挙", "An error occurred while creating the connector": "コネクタの作成中にエラーが発生しました。", + "An error occurred while importing Synchronizer configuration.": "シンクロナイザー設定のインポート中にエラーが発生しました。", + "An error occurred while importing Taxii Feed configuration.": "Taxii Feed設定のインポート中にエラーが発生しました。", "An error occurred while retrieving data for this widget:": "このウィジェットのデータ取得中にエラーが発生しました:", "An error occurred while updating the connector": "コネクタの更新中にエラーが発生しました", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "エンティティ X のインスタンス トリガーは、次のイベントを通知します: X の更新/削除、X からの/へのリレーションシップの作成/削除、参照に X を持つエンティティの作成/削除 (たとえば、X を含む、X と共有されている、X によって作成されている...)、エンティティの参照で X を追加/削除します。", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "インジケータを基にしたオブザーバブルの作成", "Create observables from all indicators in the bundle": "バンドル内のすべてのインジケータからオブザーバブルを作成する", "Create observables from this indicator": "このインジケータから観測値を作成する", + "Create OpenCTI Stream": "OpenCTI ストリームの作成", "Create priority intelligence requirement": "優先情報要件の作成", "Create relations in bulk": "リレーションの一括作成", "Create relations in bulk for": "リレーションシップの一括作成", @@ -1060,6 +1065,7 @@ "Disable forces": "強制モードをやめる", "Disable horizontal tree mode": "水平ツリーモードを無効にする", "Disable invert colors": "色の反転を無効にする", + "Disable on-the-fly entity creation": "オンザフライ・エンティティ生成を無効にする", "Disable public dashboard": "公開ダッシュボードを無効にする", "Disable public link": "公開リンクを無効にする", "Disable timeout on this page": "このページでタイムアウトを無効にする", @@ -1918,12 +1924,12 @@ "Heatmap": "ヒートマップ", "Height": "身長", "here": "ここ", + "Hidden": "隠された", "Hidden entity types": "非表示のエンティティ種別", "Hidden in ": "に隠されています", "Hidden in groups": "グループに隠れている", "Hidden in interface": "インターフェイスに隠されている", "Hidden in organizations": "組織に隠れて", - "Hidden": "隠された", "Hide": "隠す", "Hide column": "列を隠す", "Hide entities in container": "コンテナ内のエンティティを隠す", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "CSVフィードのインポート", "Import a CSV mapper": "CSV マッパーをインポートする", "Import a JSON mapper": "JSONマッパーのインポート", + "Import a Taxii Feed": "Taxiiフィードのインポート", "Import a template": "テンプレートのインポート", "Import a theme": "テーマのインポート", "Import a widget": "ウィジェットをインポートする", "Import a Widget": "ウィジェットのインポート", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "すべてのデータを新しいドラフトまたは分析者ワークベンチにインポートし、取り込む前にデータを検証する。複数のファイルが選択されている場合、ワークベンチを作成することはできません。", + "Import an OpenCTI Stream": "OpenCTI ストリームのインポート", "Import dashboard": "ダッシュボードのインポート", "Import data": "インポート対象データ", "Import files": "ファイルをインポート", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "もし何もリストされていなければ、全ての指標となる主要な観測可能な型にマッチします。", "Matrix in line view": "マトリックス・インライン・ビュー", "Matrix view": "マトリックス表示", + "Max concurrent sessions (0 equals no maximum)": "最大同時セッション数(0は最大なし)", "Max Confidence": "マックス・コンフィデンス", "Max Confidence is overridden for some entity types:": "エンティティの種類によっては、最大信頼度がオーバーライドされます:", "Max Confidence Level": "最大信頼度", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "最も関連性が高い - 100", "Most targeted victims (Last 3 months)": "最も標的とされた被害者 (過去 3 ヶ月)", "Most used filters": "最も使用されるフィルター", + "Move down": "下に移動", + "Move up": "上に移動", "Multi-Select": "複数選択", "Multiple": "複数", "Multiple (limited to 5)": "複数(5回まで)", @@ -3267,7 +3278,6 @@ "Remove from the container": "コンテナから削除する", "Remove from this entity": "このエンティティから削除する", "Remove from this object": "このオブジェクトから削除する", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "削除操作は、エンリッチメントやその他の知識操作など、このプレイブックのコンテキストで追加されたフィールド値にのみ適用されます。", "Remove selected items": "選択したアイテムを削除する", "Remove this reaction point": "この反応点を削除する", "REMOVE_AUTH_MEMBERS": "メンバー削除", @@ -3276,8 +3286,6 @@ "Renew": "更新", "REPLACE": "置き換える", "Replace": "置換", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "新しい著者を持つエンティティの信頼度が、古い著者を持つエンティティの信頼度よりも高ければ、置換操作は著者を効果的に置き換える。", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "置換操作は、エンリッチメントやその他の知識操作など、このプレイブックのコンテキストで追加されたフィールド値を効果的に置き換えますが、値がすでにプラットフォームに書き込まれている場合にのみ追加されます。", "Report": "レポート", "Report actions": "活動報告", "Report Confidence (RC)": "報告信頼度(RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "テキストエリア", - "Thank you!": "ありがとうございます!", "Thank you for reaching out, we'll get back to you shortly.": "ご連絡ありがとうございます。ご連絡を承りました。", + "Thank you!": "ありがとうございます!", "The alias has been added": "別名を追加しました", "The alias has been removed": "エイリアスは削除されました", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "推論された関係の各出現がこのウィジェットのためにカウントされるので,結果の量は,データ/関係によって異なる可能性がある.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "この機能を使用するには、EEライセンスを有効にする必要があります。", "You need to validate your two-factor authentication. Please type the code generated in your application": "二要素認証を検証する必要があります。アプリケーションで生成されたコードを入力してください。", "You see only marking definitions that can be shared (defined by the admin)": "共有可能な(管理者によって定義された)マーキング定義のみが表示されます。", + "You should activate EE to use this feature": "この機能を使用するにはEEを有効にする必要がある", "You should provide a variable name": "変数名を指定してください", "You will be able to revert this change if needed. ": "必要に応じて、この変更を元に戻すことができます。", "You will be automatically logged out at end of the timer.": "タイマーが終了すると自動的にログアウトされます", @@ -4541,4 +4550,4 @@ "Zoom": "ズーム", "Zoom in": "ズームイン", "Zoom out": "ズームアウト" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index d2cd009e4a03..ae3a9d864494 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "에이전트 AI(아리안 어시스턴트)", "Agentic AI capabilities": "에이전트 AI 기능", "AI Insights": "AI 인사이트", + "AI is not enabled": "AI가 활성화되지 않았습니다", "AI Powered": "AI 지원", "AI Summary": "AI 요약", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "OpenCTI 6.0부터 그룹과 사용자에게 최대 신뢰 수준이 추가됩니다. 이는 플랫폼 관리자에게 가져온 데이터의 영향을 더 많이 제어할 수 있도록 하며 데이터의 원치 않는 업데이트를 방지합니다. 자세한 내용은 {link_blogpost}을(를) 참조하십시오.\n \n이 플랫폼에서는 일부 그룹의 '최대 신뢰 수준'이 현재 정의되지 않았습니다. 그들의 멤버는 플랫폼의 버전 6.X에서 데이터를 생성할 수 없습니다. 이를 방지하려면 모든 그룹에 최대 신뢰 수준이 설정되어 있는지 확인하십시오. 자세한 내용은 {link_blogpost}을 참조하십시오. 커넥터, 피드 및 스트림과 연결된 사용자에 특히 주의해야 합니다. 필요 시 개별 신뢰 수준을 설정하여 그룹 신뢰 수준을 재정의할 수 있습니다. 도움이 필요하면 {link_slack}에 참여하십시오.", @@ -221,6 +222,7 @@ "All types of target": "모든 대상 유형", "All years": "모든 연도", "Allow modification of sensitive configuration": "민감한 구성의 수정 허용", + "Allow multiple files": "여러 파일 허용", "Allow multiple instances": "여러 인스턴스 허용", "Allow multiple instances of main entity": "주 엔터티의 여러 인스턴스 허용", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "피드, 대시보드, 플레이북 등과 같은 원클릭 위협 관리 리소스에 배포할 수 있습니다.", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Windows 서비스 상태의 열거형", "An enumeration of Windows service types": "Windows 서비스 유형 열거", "An error occurred while creating the connector": "커넥터를 만드는 동안 오류가 발생했습니다", + "An error occurred while importing Synchronizer configuration.": "동기화 프로그램 구성을 가져오는 동안 오류가 발생했습니다.", + "An error occurred while importing Taxii Feed configuration.": "Taxii 피드 구성을 가져오는 동안 오류가 발생했습니다.", "An error occurred while retrieving data for this widget:": "이 위젯의 데이터를 검색하는 동안 오류가 발생했습니다:", "An error occurred while updating the connector": "커넥터를 업데이트하는 동안 오류가 발생했습니다", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "엔터티 X에 대한 인스턴스 트리거는 다음 이벤트를 알립니다: X의 업데이트/삭제, X로부터/로의 관계 생성/삭제, 참조에 X를 포함하는 엔터티의 생성/삭제 (예: X를 포함함, X와 공유됨, X에 의해 생성됨...), 엔터티의 참조에 X 추가/제거.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "인디케이터를 기반으로 관찰 가능 항목 만들기", "Create observables from all indicators in the bundle": "번들에 있는 모든 지표에서 관찰 가능 항목 만들기", "Create observables from this indicator": "이 지표에서 관찰 가능 항목 생성", + "Create OpenCTI Stream": "OpenCTI 스트림 생성", "Create priority intelligence requirement": "우선 순위 인텔리전스 요구 사항 만들기", "Create relations in bulk": "대량으로 관계 만들기", "Create relations in bulk for": "다음에 대한 대량 관계 만들기", @@ -1060,6 +1065,7 @@ "Disable forces": "강제 비활성화", "Disable horizontal tree mode": "수평 트리 모드 비활성화", "Disable invert colors": "반전 색상 비활성화", + "Disable on-the-fly entity creation": "즉석 엔티티 생성 비활성화", "Disable public dashboard": "공개 대시보드 비활성화", "Disable public link": "공개 링크 비활성화", "Disable timeout on this page": "이 페이지에서 타임아웃 비활성화", @@ -1918,12 +1924,12 @@ "Heatmap": "히트맵", "Height": "키", "here": "여기", + "Hidden": "숨겨져 있나요", "Hidden entity types": "숨겨진 엔터티 유형", "Hidden in ": "숨겨진 ", "Hidden in groups": "그룹에서 숨겨짐", "Hidden in interface": "인터페이스에서 숨겨짐", "Hidden in organizations": "조직에서 숨겨짐", - "Hidden": "숨겨져 있나요", "Hide": "숨기기", "Hide column": "열 숨기기", "Hide entities in container": "컨테이너에서 엔티티 숨기기", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "CSV 피드 가져오기", "Import a CSV mapper": "CSV 매퍼 가져오기", "Import a JSON mapper": "JSON 매퍼 가져오기", + "Import a Taxii Feed": "택시 피드 가져오기", "Import a template": "템플릿 가져오기", "Import a theme": "테마 가져오기 중", "Import a widget": "위젯 가져오기", "Import a Widget": "위젯 가져오기", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "모든 데이터를 새 초안 또는 분석가 워크벤치로 가져와서 수집 전에 데이터의 유효성을 검사합니다. 여러 파일을 선택한 경우에는 워크벤치를 만들 수 없습니다.", + "Import an OpenCTI Stream": "OpenCTI 스트림 가져오기", "Import dashboard": "대시보드 가져오기", "Import data": "데이터 가져오기", "Import files": "파일 가져오기", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "아무 것도 나열되지 않은 경우 모든 지표 주요 관찰 유형과 일치합니다.", "Matrix in line view": "매트릭스 인라인 보기", "Matrix view": "매트릭스 보기", + "Max concurrent sessions (0 equals no maximum)": "최대 동시 세션(0은 최대값 없음)", "Max Confidence": "최대 신뢰도", "Max Confidence is overridden for some entity types:": "일부 엔터티 유형에 대해 최대 신뢰도가 재정의되었습니다:", "Max Confidence Level": "최대 신뢰도 수준", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "가장 관련성 높음 - 100", "Most targeted victims (Last 3 months)": "가장 많이 표적이 된 피해자 수(최근 3개월)", "Most used filters": "가장 많이 사용된 필터", + "Move down": "아래로 이동", + "Move up": "위로 이동", "Multi-Select": "다중 선택", "Multiple": "다중", "Multiple (limited to 5)": "다중 (최대 5개 제한)", @@ -3267,7 +3278,6 @@ "Remove from the container": "컨테이너에서 제거", "Remove from this entity": "이 엔터티에서 제거", "Remove from this object": "이 객체에서 제거", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "제거 작업은 보강 또는 기타 지식 조작과 같이 이 플레이북의 컨텍스트에서 추가된 필드 값에만 적용되며, 플랫폼에 이미 값이 작성된 경우에는 적용되지 않습니다.", "Remove selected items": "선택한 항목 제거", "Remove this reaction point": "이 반응점 제거", "REMOVE_AUTH_MEMBERS": "REMOVE_AUTH_MEMBERS", @@ -3276,8 +3286,6 @@ "Renew": "갱신", "REPLACE": "REPLACE", "Replace": "교체", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "새 작성자가 있는 엔티티의 신뢰 수준이 이전 작성자가 있는 엔티티의 신뢰 수준보다 높으면 바꾸기 작업을 통해 작성자를 효과적으로 교체할 수 있습니다.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "바꾸기 작업은 보강 또는 기타 지식 조작과 같이 이 플레이북의 컨텍스트에서 추가된 이 필드 값을 효과적으로 바꾸지만 플랫폼에 값이 이미 작성된 경우에만 값을 추가합니다.", "Report": "보고서", "Report actions": "보고서 작업", "Report Confidence (RC)": "보고서 신뢰도(RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "텍스트 영역", - "Thank you!": "감사합니다!", "Thank you for reaching out, we'll get back to you shortly.": "감사합니다. 곧 연락드리겠습니다.", + "Thank you!": "감사합니다!", "The alias has been added": "별명이 추가되었습니다", "The alias has been removed": "별명이 제거되었습니다", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "이 위젯에서는 추론된 관계가 발생할 때마다 계산되므로 결과의 양은 데이터/관계에 따라 달라질 수 있습니다.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "이 기능을 사용하려면 EE 라이선스를 활성화해야 합니다", "You need to validate your two-factor authentication. Please type the code generated in your application": "이중 인증을 검증해야 합니다. 애플리케이션에서 생성된 코드를 입력하십시오", "You see only marking definitions that can be shared (defined by the admin)": "공유할 수 있는 마킹 정의만 표시됩니다 (관리자가 정의한 것)", + "You should activate EE to use this feature": "이 기능을 사용하려면 EE를 활성화해야 합니다", "You should provide a variable name": "변수 이름을 입력해야 합니다", "You will be able to revert this change if needed. ": "필요한 경우 이 변경 사항을 되돌릴 수 있습니다.", "You will be automatically logged out at end of the timer.": "타이머가 끝나면 자동으로 로그아웃됩니다.", @@ -4541,4 +4550,4 @@ "Zoom": "확대/축소", "Zoom in": "확대", "Zoom out": "축소" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/ru.json b/opencti-platform/opencti-front/lang/front/ru.json index 7b4effa552e3..bef03bd14fae 100644 --- a/opencti-platform/opencti-front/lang/front/ru.json +++ b/opencti-platform/opencti-front/lang/front/ru.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "Агентный искусственный интеллект (помощник Ариана)", "Agentic AI capabilities": "Возможности агентного ИИ", "AI Insights": "Инсайты ИИ", + "AI is not enabled": "ИИ не включен", "AI Powered": "На базе ИИ", "AI Summary": "Краткое резюме ИИ", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL", @@ -221,6 +222,7 @@ "All types of target": "Все типы мишеней", "All years": "Все годы", "Allow modification of sensitive configuration": "Позволяет изменять конфиденциальную конфигурацию", + "Allow multiple files": "Разрешить несколько файлов", "Allow multiple instances": "Разрешить несколько экземпляров", "Allow multiple instances of main entity": "Разрешить несколько экземпляров главной сущности", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "позволяют в один клик развернуть ресурсы для управления угрозами, такие как фиды, панели управления, плейбуки и т. д.", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Перечисление статусов служб Windows", "An enumeration of Windows service types": "Перечисление типов служб Windows", "An error occurred while creating the connector": "При создании коннектора произошла ошибка", + "An error occurred while importing Synchronizer configuration.": "При импорте конфигурации синхронизатора произошла ошибка.", + "An error occurred while importing Taxii Feed configuration.": "При импорте конфигурации Taxii Feed произошла ошибка.", "An error occurred while retrieving data for this widget:": "При получении данных для этого виджета произошла ошибка:", "An error occurred while updating the connector": "При обновлении коннектора произошла ошибка", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "Триггер экземпляра на сущности X уведомляет о следующих событиях: обновление/удаление X, создание/удаление отношения от/к X, создание/удаление сущности, которая имеет X в своих рефсах (например, содержит X, разделяется с X, создается X...), добавление/удаление X в рефсах сущности.", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "Создание наблюдаемых на основе индикатора", "Create observables from all indicators in the bundle": "Создание наблюдаемых на основе всех индикаторов в связке", "Create observables from this indicator": "Создайте наблюдаемые объекты на основе этого индикатора", + "Create OpenCTI Stream": "Создание потока OpenCTI", "Create priority intelligence requirement": "Создание приоритетного требования к разведданным", "Create relations in bulk": "Создание отношений в массовом порядке", "Create relations in bulk for": "Создавайте отношения в массовом порядке для", @@ -1060,6 +1065,7 @@ "Disable forces": "Отключить силы", "Disable horizontal tree mode": "Отключить режим горизонтального дерева", "Disable invert colors": "Отключить инвертирование цветов", + "Disable on-the-fly entity creation": "Отключение создания сущностей \"на лету", "Disable public dashboard": "Отключить публичный дашборд", "Disable public link": "Отключить публичную ссылку", "Disable timeout on this page": "Отключите тайм-аут на этой странице", @@ -1918,12 +1924,12 @@ "Heatmap": "Тепловая карта", "Height": "Высота", "here": "здесь", + "Hidden": "Скрытый", "Hidden entity types": "Скрытые типы сущностей", "Hidden in ": "Скрыто в", "Hidden in groups": "Скрытые в группах", "Hidden in interface": "Скрыто в интерфейсе", "Hidden in organizations": "Скрытые в организациях", - "Hidden": "Скрытый", "Hide": "Скрыть", "Hide column": "Скрыть колонку", "Hide entities in container": "Скрыть сущности в контейнере", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "Импорт CSV-файла", "Import a CSV mapper": "Импорт отображателя CSV", "Import a JSON mapper": "Импорт JSON-маппера", + "Import a Taxii Feed": "Импорт ленты Taxii", "Import a template": "Импорт шаблона", "Import a theme": "Импортировать тему", "Import a widget": "Импорт виджета", "Import a Widget": "Импорт виджета", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "Импортируйте все данные в новый проект или рабочий стол аналитика, чтобы проверить данные перед вводом. Обратите внимание, что создание рабочего места невозможно, если выбрано несколько файлов.", + "Import an OpenCTI Stream": "Импорт потока OpenCTI", "Import dashboard": "Импорт дашборда", "Import data": "Импортные данные", "Import files": "Импорт файлов", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "Сопоставляет все основные наблюдаемые типы индикаторов, если ни один из них не указан.", "Matrix in line view": "Матрица в линейном представлении", "Matrix view": "Матричный вид", + "Max concurrent sessions (0 equals no maximum)": "Максимальное количество одновременных сеансов (0 означает отсутствие максимума)", "Max Confidence": "Максимальная уверенность", "Max Confidence is overridden for some entity types:": "Максимальное значение Confidence переопределено для некоторых типов сущностей:", "Max Confidence Level": "Максимальный уровень доверия", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "Наиболее актуально - 100", "Most targeted victims (Last 3 months)": "Часто атакуемые (последние 3 месяца)", "Most used filters": "Часто используемые фильтры", + "Move down": "Перемещение вниз", + "Move up": "Переместить вверх", "Multi-Select": "Мультивыбор", "Multiple": "Множество", "Multiple (limited to 5)": "Несколько (не более 5)", @@ -3267,7 +3278,6 @@ "Remove from the container": "Удалить из контейнера", "Remove from this entity": "Удалить из этой организации", "Remove from this object": "Удалить из этого объекта", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "Операция Remove будет применяться только к значениям полей, добавленных в контексте этого плейбука, например, при обогащении или других манипуляциях со знаниями, но не в том случае, если значения уже записаны в платформе.", "Remove selected items": "Удалить выбранные элементы", "Remove this reaction point": "Удалите эту точку реакции", "REMOVE_AUTH_MEMBERS": "REMOVE_AUTH_MEMBERS", @@ -3276,8 +3286,6 @@ "Renew": "Обновить", "REPLACE": "ЗАМЕНИТЬ", "Replace": "Заменить", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "Операция Replace эффективно заменит автора, если уровень доверия к сущности с новым автором выше, чем к сущности со старым автором.", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "Операция Replace эффективно заменит значения этого поля, добавленные в контексте данного плейбука, например, при обогащении или других манипуляциях со знаниями, но добавит их только в том случае, если значения уже записаны в платформе.", "Report": "Отчет", "Report actions": "Отчетные действия", "Report Confidence (RC)": "Report Confidence (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "текст/маркдаун", "text/plain": "текст/plain", "Textarea": "Textarea", - "Thank you!": "Спасибо!", "Thank you for reaching out, we'll get back to you shortly.": "Спасибо за обращение, мы скоро вам ответим.", + "Thank you!": "Спасибо!", "The alias has been added": "Псевдоним был добавлен", "The alias has been removed": "Псевдоним был удален", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "Количество результатов может отличаться в зависимости от данных/отношений, поскольку для этого виджета учитывается каждое появление предполагаемого отношения.", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "Для использования этой функции необходимо включить лицензию EE License.", "You need to validate your two-factor authentication. Please type the code generated in your application": "Вам необходимо подтвердить двухфакторную аутентификацию. Пожалуйста, введите код, сгенерированный в вашем приложении", "You see only marking definitions that can be shared (defined by the admin)": "Вы видите только те определения маркировки, которые могут быть доступны (определены администратором).", + "You should activate EE to use this feature": "Чтобы использовать эту функцию, необходимо активировать EE", "You should provide a variable name": "Вы должны указать имя переменной", "You will be able to revert this change if needed. ": "При необходимости вы сможете отменить это изменение.", "You will be automatically logged out at end of the timer.": "По окончании таймера вы автоматически выйдете из системы.", @@ -4541,4 +4550,4 @@ "Zoom": "Zoom", "Zoom in": "Увеличить", "Zoom out": "Уменьшить масштаб" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index e20b23409590..5cbb134436d9 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "人工智能代理(阿丽亚娜助理)", "Agentic AI capabilities": "人工智能代理能力", "AI Insights": "人工智能洞察", + "AI is not enabled": "未启用人工智能", "AI Powered": "人工智能技术", "AI Summary": "人工智能摘要", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "从 OpenCTI 6.0 开始,将为组和用户添加最大置信度。这将为平台管理员提供更多对导入数据影响的控制,并防止任何不必要的数据更新。请阅读 {link_blogpost} 获取更多信息。\n \n在本平台中,某些组的 \"Max Confidence Level\" 目前未定义。这些组的成员将无法在 6.X 版平台中创建任何数据。为避免出现这种情况,请确保所有组都设置了最大置信度。更多信息请参见 {link_blogpost}。请特别注意与连接器、馈送和数据流相关联的用户;必要时,您可以设置个人置信度来覆盖组置信度。如果需要建议,请加入 {link_slack} 。", @@ -221,6 +222,7 @@ "All types of target": "所有类型的目标", "All years": "所有年份", "Allow modification of sensitive configuration": "允许修改敏感配置", + "Allow multiple files": "允许多个文件", "Allow multiple instances": "允许多个实例", "Allow multiple instances of main entity": "允许主实体有多个实例", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "允许一键部署威胁管理资源,如馈送、仪表板、播放簿等。", @@ -243,6 +245,8 @@ "An enumeration of Windows service statuses": "Windows 服务状态枚举", "An enumeration of Windows service types": "Windows 服务类型枚举", "An error occurred while creating the connector": "创建连接器时发生错误", + "An error occurred while importing Synchronizer configuration.": "导入同步器配置时发生错误。", + "An error occurred while importing Taxii Feed configuration.": "导入 Taxii Feed 配置时发生错误。", "An error occurred while retrieving data for this widget:": "在检索此 widget 的数据时发生错误:", "An error occurred while updating the connector": "更新连接器时发生错误", "An instance trigger on an entity X notifies the following events: update/deletion of X, creation/deletion of a relationship from/to X, creation/deletion of an entity that has X in its refs (for instance contains X, is shared with X, is created by X...), adding/removing X in the ref of an entity.": "实体 X 的实例触发器会通知以下事件:更新/删除 X、创建/删除与 X 的关系、创建/删除在其引用中包含 X 的实体(例如包含 X、与 X 共享、由 X 创建......)、在实体的引用中添加/删除 X。", @@ -805,6 +809,7 @@ "Create observables based on an indicator": "基于指标创建可观测变量", "Create observables from all indicators in the bundle": "从捆绑包中的所有观测指标创建观测指标", "Create observables from this indicator": "从该指标创建观测值", + "Create OpenCTI Stream": "创建 OpenCTI 流", "Create priority intelligence requirement": "创建优先情报需求", "Create relations in bulk": "批量创建关系", "Create relations in bulk for": "批量创建关系", @@ -1060,6 +1065,7 @@ "Disable forces": "禁用强制", "Disable horizontal tree mode": "禁用水平树模式", "Disable invert colors": "禁用反转颜色", + "Disable on-the-fly entity creation": "禁用即时创建实体", "Disable public dashboard": "禁用公共仪表盘", "Disable public link": "禁用公共链接", "Disable timeout on this page": "在此页面上禁用超时", @@ -1918,12 +1924,12 @@ "Heatmap": "热图", "Height": "高度", "here": "这里", + "Hidden": "隐藏", "Hidden entity types": "隐藏的实体类型", "Hidden in ": "隐藏在", "Hidden in groups": "隐藏在群体中", "Hidden in interface": "隐藏在界面中", "Hidden in organizations": "隐藏在组织中", - "Hidden": "隐藏", "Hide": "隐藏", "Hide column": "隐藏栏", "Hide entities in container": "隐藏容器中的实体", @@ -1995,11 +2001,13 @@ "Import a CSV Feed": "导入 CSV 饲料", "Import a CSV mapper": "导入 CSV 映射器", "Import a JSON mapper": "导入 JSON 映射器", + "Import a Taxii Feed": "导入 Taxii Feed", "Import a template": "导入模板", "Import a theme": "导入主题", "Import a widget": "导入小部件", "Import a Widget": "导入小工具", "Import all data into a new draft or an analyst workbench, to validate the data before ingestion. Note that creating a workbench is not possible when several files are selected.": "将所有数据导入新草稿或分析师工作台,以便在导入前验证数据。请注意,如果选择了多个文件,则无法创建工作台。", + "Import an OpenCTI Stream": "导入 OpenCTI 流", "Import dashboard": "导入仪表板", "Import data": "导入数据", "Import files": "导入文件", @@ -2458,6 +2466,7 @@ "Matches all indicator main observable types if none is listed.": "如果没有列出主要观测指标类型,则匹配所有指标类型。", "Matrix in line view": "矩阵内联视图", "Matrix view": "矩阵视图", + "Max concurrent sessions (0 equals no maximum)": "最大并发会话数(0 表示无最大值)", "Max Confidence": "最大置信度", "Max Confidence is overridden for some entity types:": "对于某些实体类型,最大置信度会被覆盖:", "Max Confidence Level": "最大置信度", @@ -2547,6 +2556,8 @@ "Most relevant - 100": "最相关 - 100", "Most targeted victims (Last 3 months)": "最多目标受害者(最近 3 个月)", "Most used filters": "最常用的过滤器", + "Move down": "向下移动", + "Move up": "上移", "Multi-Select": "多选", "Multiple": "多个", "Multiple (limited to 5)": "多次(限 5 次)", @@ -3267,7 +3278,6 @@ "Remove from the container": "从容器中取出", "Remove from this entity": "从该实体中移除", "Remove from this object": "从该对象中移除", - "Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.": "移除操作仅适用于在此播放簿中添加的字段值,如充实或其他知识操作,但不适用于已写入平台的值。", "Remove selected items": "移除选中项", "Remove this reaction point": "删除该反应点", "REMOVE_AUTH_MEMBERS": "remove_auth_members", @@ -3276,8 +3286,6 @@ "Renew": "更新", "REPLACE": "替换", "Replace": "替换", - "Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.": "如果新作者实体的置信度高于旧作者实体的置信度,则替换操作将有效替换作者。", - "Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.": "替换(Replace)操作将有效地替换在此播放列表中添加的字段值,如充实或其他知识操作,但只有在值已写入平台的情况下才会附加这些值。", "Report": "报告", "Report actions": "报告行动", "Report Confidence (RC)": "报告可信度 (RC)", @@ -3802,8 +3810,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "文本区域", - "Thank you!": "感谢您的联系", "Thank you for reaching out, we'll get back to you shortly.": "感谢您的联系,我们会尽快回复您。", + "Thank you!": "感谢您的联系", "The alias has been added": "别名已添加", "The alias has been removed": "别名已删除", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "结果的数量可能因数据/关系而异,因为推断关系的每次出现都会计入该 widget 中。", @@ -4515,6 +4523,7 @@ "You need to enable EE License to use this feature": "您需要启用 EE 许可才能使用此功能", "You need to validate your two-factor authentication. Please type the code generated in your application": "您需要验证您的双因素身份验证。请输入在您的应用程序中生成的代码。", "You see only marking definitions that can be shared (defined by the admin)": "您只能看到可以共享的标记定义(由管理员定义)", + "You should activate EE to use this feature": "您应激活 EE 才能使用此功能", "You should provide a variable name": "您应该提供一个变量名", "You will be able to revert this change if needed. ": "如果需要,您可以恢复这一更改。", "You will be automatically logged out at end of the timer.": "计时器结束时您将自动注销", @@ -4541,4 +4550,4 @@ "Zoom": "放大", "Zoom in": "放大", "Zoom out": "缩小" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 6e7d03088a6d..b82d1f6c9b0e 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -1,6 +1,6 @@ { "name": "opencti-front", - "version": "6.9.3", + "version": "6.9.9", "private": true, "workspaces": [ "packages/*" @@ -35,7 +35,7 @@ "@analytics/google-analytics": "1.1.0", "@cfworker/json-schema": "4.1.1", "@ckeditor/ckeditor5-react": "9.5.0", - "@filigran/chatbot": "1.0.2", + "@filigran/chatbot": "1.1.0", "@fontsource/geologica": "5.2.8", "@fontsource/ibm-plex-sans": "5.2.8", "@graphiql/toolkit": "0.11.3", @@ -71,7 +71,7 @@ "graphiql": "4.1.2", "graphql": "16.12.0", "graphql-ws": "5.16.2", - "html-react-parser": "5.2.10", + "html-react-parser": "5.2.11", "html-to-image": "1.11.13", "html-to-pdfmake": "2.5.32", "invert-color": "2.0.0", @@ -85,7 +85,7 @@ "mdi-material-ui": "7.9.4", "moment": "2.30.1", "moment-timezone": "0.6.0", - "pdfmake": "0.2.20", + "pdfmake": "0.2.21", "prop-types": "15.8.1", "qrcode": "1.5.4", "ramda": "0.32.0", @@ -98,7 +98,7 @@ "react-dom": "19.2.3", "react-draggable": "4.5.0", "react-force-graph-2d": "1.29.0", - "react-force-graph-3d": "1.24.4", + "react-force-graph-3d": "1.29.0", "react-grid-layout": "1.5.3", "react-intl": "7.1.14", "react-leaflet": "5.0.0", @@ -106,11 +106,11 @@ "react-material-ui-carousel": "3.4.2", "react-mde": "11.5.0", "react-otp-input": "3.1.1", - "react-pdf": "10.2.0", + "react-pdf": "10.3.0", "react-rectangle-selection": "1.0.4", "react-relay": "20.1.1", "react-relay-network-modern": "6.2.2", - "react-router-dom": "6.30.2", + "react-router-dom": "6.30.3", "react-syntax-highlighter": "16.1.0", "react-transition-group": "4.4.5", "react-virtualized": "9.22.6", @@ -124,34 +124,34 @@ "rxjs": "7.8.2", "three-spritetext": "1.10.0", "use-analytics": "1.1.0", - "uuid": "11.1.0", + "uuid": "13.0.0", "yup": "1.7.1" }, "devDependencies": { "@eslint/js": "9.39.2", - "@faker-js/faker": "10.1.0", + "@faker-js/faker": "10.2.0", "@playwright/test": "1.57.0", - "@stylistic/eslint-plugin": "5.6.1", + "@stylistic/eslint-plugin": "5.7.0", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "16.3.1", "@testing-library/user-event": "14.6.1", "@types/d3-scale": "4.0.9", "@types/html-to-pdfmake": "2.4.4", - "@types/node": "22.19.3", + "@types/node": "22.19.7", "@types/qrcode": "1.5.6", "@types/ramda": "0.31.1", - "@types/react": "19.2.7", + "@types/react": "19.2.8", "@types/react-csv": "1.1.10", "@types/react-dom": "19.2.3", "@types/react-grid-layout": "1.3.6", "@types/react-relay": "18.2.1", "@types/react-syntax-highlighter": "15.5.13", "@types/react-transition-group": "4.4.12", - "@types/relay-runtime": "20.1.0", + "@types/relay-runtime": "20.1.1", "@types/relay-test-utils": "19.0.0", "@vitejs/plugin-react": "5.1.2", - "@vitest/coverage-v8": "4.0.16", + "@vitest/coverage-v8": "4.0.17", "babel-plugin-relay": "20.1.1", "chokidar": "5.0.0", "compression": "1.8.1", @@ -163,20 +163,20 @@ "eslint-plugin-import-newlines": "1.4.0", "eslint-plugin-react": "7.37.5", "express": "5.2.1", - "globals": "16.5.0", + "globals": "17.0.0", "http-proxy-middleware": "3.0.5", "i18n-auto-translation": "2.2.3", - "jsdom": "27.3.0", + "jsdom": "27.4.0", "license-checker-rseidelsohn": "4.4.2", "monocart-reporter": "2.9.23", "relay-compiler": "20.1.1", "relay-test-utils": "20.1.1", "typescript": "5.9.3", - "typescript-eslint": "8.50.0", - "vite": "7.3.0", + "typescript-eslint": "8.53.0", + "vite": "7.3.1", "vite-plugin-relay": "2.1.0", "vite-plugin-static-copy": "3.1.4", - "vitest": "4.0.16" + "vitest": "4.0.17" }, "packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8", "engines": { @@ -188,7 +188,8 @@ "glob-parent": "6.0.2", "json5": "2.2.3", "react": "19.2.3", - "webpack": "5.104.1" + "webpack": "5.104.1", + "three": "0.181.2" }, "dependenciesMeta": { "canvas": { diff --git a/opencti-platform/opencti-front/src/components/AutocompleteField.jsx b/opencti-platform/opencti-front/src/components/AutocompleteField.jsx deleted file mode 100644 index a55abdce2fa7..000000000000 --- a/opencti-platform/opencti-front/src/components/AutocompleteField.jsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react'; -import TextField from '@mui/material/TextField'; -import IconButton from '@mui/material/IconButton'; -import { Add } from '@mui/icons-material'; -import MUIAutocomplete from '@mui/material/Autocomplete'; -import { fieldToAutocomplete } from 'formik-mui'; -import { useField } from 'formik'; -import { isNil } from 'ramda'; -import { truncate } from '../utils/String'; -import { useFormatter } from './i18n'; - -const AutocompleteField = (props) => { - // Separate props used only for this component - // and props to be passed to MUI Autocomplete. - const { - optionLength = 40, - ...otherProps - } = props; - const { - form: { setFieldValue, setFieldTouched, submitCount }, - field: { name }, - onChange, - onFocus, - noOptionsText, - renderOption, - required = false, - isOptionEqualToValue, - textfieldprops, - openCreate, - getOptionLabel, - onInternalChange, - endAdornment, - } = otherProps; - const [, meta] = useField(name); - const { t_i18n } = useFormatter(); - const internalOnChange = React.useCallback( - (_, value) => { - if (typeof onInternalChange === 'function') { - onInternalChange(name, value || ''); - } else { - setFieldValue(name, value); - if (typeof onChange === 'function') { - onChange(name, value || ''); - } - } - }, - [setFieldValue, name, onChange], - ); - const internalOnFocus = React.useCallback(() => { - if (typeof onFocus === 'function') { - onFocus(name); - } - }, [onFocus, name]); - const internalOnBlur = React.useCallback(() => { - setFieldTouched(name, true); - }, [setFieldTouched]); - const fieldProps = fieldToAutocomplete(otherProps); - delete fieldProps.helperText; - delete fieldProps.openCreate; - // Properly handle no selected option - if (fieldProps.value === '') { - fieldProps.value = null; - } - const defaultOptionToValue = (option, value) => option.value === value.value; - const defaultGetOptionLabel = (option) => ( - typeof option === 'object' ? truncate(option.label, optionLength) : truncate(option, optionLength) - ); - - const showError = !isNil(meta.error) && (meta.touched || submitCount > 0); - - return ( -
- ( - - )} - onChange={internalOnChange} - onFocus={internalOnFocus} - onBlur={internalOnBlur} - isOptionEqualToValue={isOptionEqualToValue ?? defaultOptionToValue} - slotProps={{ - paper: { - elevation: 2, - }, - }} - /> - {typeof openCreate === 'function' && ( - openCreate()} - edge="end" - style={{ position: 'absolute', top: 5, right: 35 }} - size="large" - title={t_i18n('Add')} - > - - - )} -
- ); -}; - -export default AutocompleteField; diff --git a/opencti-platform/opencti-front/src/components/AutocompleteField.tsx b/opencti-platform/opencti-front/src/components/AutocompleteField.tsx new file mode 100644 index 000000000000..40b389b2e552 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/AutocompleteField.tsx @@ -0,0 +1,154 @@ +import { ReactNode, useCallback } from 'react'; +import { Add } from '@mui/icons-material'; +import { TextField, IconButton, Autocomplete, AutocompleteProps, TextFieldProps, AutocompleteValue } from '@mui/material'; +import { FieldProps, useField } from 'formik'; +import { truncate } from '../utils/String'; +import { useFormatter } from './i18n'; +import { isNilField } from '../utils/utils'; +import { FieldOption } from '../utils/field'; +import { fieldToAutocomplete } from 'formik-mui'; + +type Bool = boolean | undefined; +type PossibleValue = FieldOption | string; + +export type AutocompleteFieldProps< + M extends Bool = true, + Value extends PossibleValue = FieldOption, + DC extends Bool = boolean, + FSolo extends Bool = false, +> = Omit, 'onChange' | 'onBlur' | 'onFocus' | 'renderInput'> + & FieldProps + & { + optionLength?: number; + required?: boolean; + endAdornment?: ReactNode; + textfieldprops?: TextFieldProps; + onFocus?: (name: string) => void; + onChange?: (name: string, value: AutocompleteValue) => void; + onInternalChange?: (name: string, value: AutocompleteValue) => void; + openCreate?: () => void; + }; + +const AutocompleteField = < + M extends Bool = true, + Value extends PossibleValue = FieldOption, + DC extends Bool = boolean, + FSolo extends Bool = false, +>({ + optionLength = 40, + required = false, + onChange, + onFocus, + onInternalChange, + openCreate, + ...muiProps +}: AutocompleteFieldProps) => { + type MuiProps = AutocompleteProps; + + const { + form: { setFieldValue, setFieldTouched, submitCount }, + field: { name }, + noOptionsText, + renderOption, + isOptionEqualToValue, + textfieldprops, + getOptionLabel, + endAdornment, + disabled, + } = muiProps; + + const [, meta] = useField(name); + const { t_i18n } = useFormatter(); + + const internalOnChange = useCallback>((_, value) => { + if (onInternalChange) { + onInternalChange(name, value); + } else { + setFieldValue(name, value); + onChange?.(name, value); + } + }, [setFieldValue, name, onChange, onInternalChange]); + + const internalOnFocus = useCallback>(() => { + onFocus?.(name); + }, [onFocus, name]); + + const internalOnBlur = useCallback>(() => { + setFieldTouched(name, true); + }, [setFieldTouched]); + + const defaultOptionToValue = (option: Value, value: Value) => { + const optionVal = typeof option === 'object' ? option.value : option; + const valueVal = typeof value === 'object' ? value.value : value; + return optionVal === valueVal; + }; + + const defaultGetOptionLabel: MuiProps['getOptionLabel'] = (option) => { + return typeof option === 'object' + ? truncate(option.label, optionLength) + : truncate(option, optionLength); + }; + + const helperText = textfieldprops?.helperText; + const showError = !isNilField(meta.error) && (meta.touched || submitCount > 0); + const fieldProps = fieldToAutocomplete({ + ...muiProps, + renderInput: ({ inputProps: { value, ...inputProps }, InputProps, ...params }) => ( + + ), + }); + + return ( +
+ + + {openCreate && ( + openCreate()} + edge="end" + style={{ position: 'absolute', top: 5, right: 35 }} + size="large" + title={t_i18n('Add')} + > + + + )} +
+ ); +}; + +export default AutocompleteField; diff --git a/opencti-platform/opencti-front/src/components/SearchInput.jsx b/opencti-platform/opencti-front/src/components/SearchInput.jsx index dcf528f3193f..e0c82a4e3679 100644 --- a/opencti-platform/opencti-front/src/components/SearchInput.jsx +++ b/opencti-platform/opencti-front/src/components/SearchInput.jsx @@ -39,6 +39,8 @@ const useStyles = makeStyles((theme) => ({ borderRadius: 4, padding: '0 10px 0 10px', height: 30, + width: '100%', + minWidth: 100, }, searchRootThin: { borderRadius: 4, @@ -54,6 +56,9 @@ const useStyles = makeStyles((theme) => ({ searchInputTopBar: { width: '100%', }, + searchInputInDrawer: { + width: '100%', + }, searchInput: { transition: theme.transitions.create('width'), width: 200, @@ -117,6 +122,8 @@ const SearchInput = (props) => { classInput = classes.searchInputTopBar; } else if (variant === 'noAnimation') { classInput = classes.searchInputNoAnimation; + } else if (variant === 'inDrawer') { + classInput = classes.searchInputInDrawer; } const handleChangeAskAI = () => { diff --git a/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx b/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx index 774fa40542d8..90e863040c50 100644 --- a/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx +++ b/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx @@ -66,8 +66,10 @@ const PeriodicityField: React.FC = ({ durationString = `P${value}W`; } else if (unit === 'D') { durationString = `P${value}D`; + } else if (unit === 'M') { + durationString = `P${value}M`; } else if (unit === 'H') { - durationString = `P${value}H`; + durationString = `PT${value}H`; } else { durationString = `PT${value}${unit}`; } diff --git a/opencti-platform/opencti-front/src/components/fields/SelectField.jsx b/opencti-platform/opencti-front/src/components/fields/SelectField.tsx similarity index 58% rename from opencti-platform/opencti-front/src/components/fields/SelectField.jsx rename to opencti-platform/opencti-front/src/components/fields/SelectField.tsx index 6e802fc46765..e8340bb464d4 100644 --- a/opencti-platform/opencti-front/src/components/fields/SelectField.jsx +++ b/opencti-platform/opencti-front/src/components/fields/SelectField.tsx @@ -1,39 +1,52 @@ import React from 'react'; import { isNil } from 'ramda'; -import { getIn, useField } from 'formik'; +import { FieldProps, getIn, useField } from 'formik'; import { v4 as uuid } from 'uuid'; import MuiSelect from '@mui/material/Select'; import InputLabel from '@mui/material/InputLabel'; import FormControl from '@mui/material/FormControl'; import FormHelperText from '@mui/material/FormHelperText'; +import { SelectProps } from '@mui/material'; -const fieldToSelect = ({ - disabled, - field: { onChange: fieldOnChange, ...field }, - form: { isSubmitting, touched, errors, setFieldTouched, setFieldValue }, - onClose, - ...props -}) => { +export type SelectFieldProps = FieldProps & Omit, 'onChange' | 'onFocus'> & { + required: boolean; + onChange?: (name: string, value: string) => void; + onFocus?: (name: string) => void; + onSubmit?: (name: string, value: string) => void; + containerstyle?: Record; + helpertext?: string; +}; + +const fieldToSelect = (muiProps: SelectFieldProps) => { + const { + disabled, + field: { onChange: fieldOnChange, ...field }, + form: { isSubmitting, touched, errors, setFieldTouched, setFieldValue }, + onClose, + } = muiProps; const fieldError = getIn(errors, field.name); const showError = getIn(touched, field.name) && !!fieldError; + return { + ...field, + ...muiProps, disabled: disabled ?? isSubmitting, error: showError, onBlur: () => {}, onChange: fieldOnChange ?? (() => {}), onClose: onClose ?? (async (e) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const { dataset } = e.target; if (dataset && dataset.value) { await setFieldValue(field.name, dataset.value); } setFieldTouched(field.name, true); }), - ...field, - ...props, }; }; -const SelectField = (props) => { +const SelectField = (muiProps: SelectFieldProps) => { const { form: { setFieldValue, setFieldTouched }, field: { name }, @@ -41,8 +54,11 @@ const SelectField = (props) => { onChange, onFocus, onSubmit, - } = props; - const internalOnChange = React.useCallback( + containerstyle, + helpertext, + } = muiProps; + + const internalOnChange = React.useCallback['onChange']>>( (event) => { const { value } = event.target; setFieldValue(name, value); @@ -52,12 +68,12 @@ const SelectField = (props) => { }, [setFieldValue, onChange, name], ); - const internalOnFocus = React.useCallback(() => { + const internalOnFocus = React.useCallback['onFocus']>>(() => { if (typeof onFocus === 'function') { onFocus(name); } }, [onFocus, name]); - const internalOnBlur = React.useCallback( + const internalOnBlur = React.useCallback['onBlur']>>( (event) => { const { value } = event.target; setFieldTouched(name, true); @@ -68,22 +84,22 @@ const SelectField = (props) => { [setFieldTouched, onSubmit, name], ); const [, meta] = useField(name); - const { value, ...otherProps } = fieldToSelect(props); + const { value, ...otherProps } = fieldToSelect(muiProps); const labelId = uuid(); return ( - {props.label} + {muiProps.label} { labelId={labelId} /> - {meta.touched && !isNil(meta.error) ? meta.error : props.helpertext} + {meta.touched && !isNil(meta.error) ? meta.error : helpertext} ); diff --git a/opencti-platform/opencti-front/src/private/Index.tsx b/opencti-platform/opencti-front/src/private/Index.tsx index eca42b5fa061..08969fa4b464 100644 --- a/opencti-platform/opencti-front/src/private/Index.tsx +++ b/opencti-platform/opencti-front/src/private/Index.tsx @@ -34,7 +34,7 @@ const RootArsenal = lazy(() => import('./components/arsenal/Root')); const RootTechnique = lazy(() => import('./components/techniques/Root')); const RootEntities = lazy(() => import('./components/entities/Root')); const RootLocation = lazy(() => import('./components/locations/Root')); -const RootData = lazy(() => import('./components/data/Root')); +const RootData = lazy(() => import('@components/data/Root')); const RootTrash = lazy(() => import('./components/trash/Root')); const RootDrafts = lazy(() => import('./components/drafts/Root')); const RootWorkspaces = lazy(() => import('./components/workspaces/Root')); diff --git a/opencti-platform/opencti-front/src/private/Root.tsx b/opencti-platform/opencti-front/src/private/Root.tsx index 4dced6a38468..40e647b6ea27 100644 --- a/opencti-platform/opencti-front/src/private/Root.tsx +++ b/opencti-platform/opencti-front/src/private/Root.tsx @@ -256,10 +256,10 @@ const meUserFragment = graphql` definition_type x_opencti_order } - # personal_notifiers { - # id - # name - # } + personal_notifiers { + id + name + } can_manage_sensitive_config } `; diff --git a/opencti-platform/opencti-front/src/private/components/analyses/external_references/AddExternalReferences.jsx b/opencti-platform/opencti-front/src/private/components/analyses/external_references/AddExternalReferences.jsx index ca36d55620a0..3844172f785e 100644 --- a/opencti-platform/opencti-front/src/private/components/analyses/external_references/AddExternalReferences.jsx +++ b/opencti-platform/opencti-front/src/private/components/analyses/external_references/AddExternalReferences.jsx @@ -64,9 +64,9 @@ const AddExternalReferences = ({ marginLeft: 'auto', marginRight: '20px', display: 'flex', - flexWrap: 'wrap', - alignItems: 'flex-end', + alignItems: 'center', justifyContent: 'flex-end', + gap: '12px', }} > @@ -297,14 +335,4 @@ const IngestionTaxiiCreation = (props) => { ); }; -IngestionTaxiiCreation.propTypes = { - paginationOptions: PropTypes.object, - classes: PropTypes.object, - theme: PropTypes.object, - t: PropTypes.func, -}; - -export default R.compose( - inject18n, - withStyles(styles, { withTheme: true }), -)(IngestionTaxiiCreation); +export default IngestionTaxiiCreation; diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiEdition.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiEdition.tsx index 3df7bc69eb42..a5dfcd461d9c 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiEdition.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiEdition.tsx @@ -5,7 +5,10 @@ import { Field, Form, Formik } from 'formik'; import MenuItem from '@mui/material/MenuItem'; import { FormikConfig } from 'formik/dist/types'; import { ExternalReferencesValues } from '@components/common/form/ExternalReferencesField'; -import { IngestionTaxiiEditionFragment_ingestionTaxii$data } from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiEditionFragment_ingestionTaxii.graphql'; +import { + IngestionTaxiiEditionFragment_ingestionTaxii$data, + IngestionTaxiiEditionFragment_ingestionTaxii$key, +} from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiEditionFragment_ingestionTaxii.graphql'; import CommitMessage from '@components/common/form/CommitMessage'; import { FieldOption, fieldSpacingContainerStyle } from '../../../../utils/field'; import CreatorField from '../../common/form/CreatorField'; @@ -28,11 +31,23 @@ import SwitchField from '../../../../components/fields/SwitchField'; import PasswordTextField from '../../../../components/PasswordTextField'; import TextField from '../../../../components/TextField'; import { useFormatter } from '../../../../components/i18n'; -import { IngestionTaxiiEditionFragment_ingestionTaxii$key } from './__generated__/IngestionTaxiiEditionFragment_ingestionTaxii.graphql'; import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import { adaptFieldValue } from '../../../../utils/String'; import useApiMutation from '../../../../utils/hooks/useApiMutation'; - +import IngestionEditionUserHandling from '@components/data/IngestionEditionUserHandling'; +export const ingestionTaxiiEditionUserHandlingPatch = graphql` + mutation IngestionTaxiiEditionUserHandlingMutation($id: ID!, $input: IngestionTaxiiAddAutoUserInput!) { + ingestionTaxiiAddAutoUser(id: $id, input: $input) { + id + name + user { + id + entity_type + name + } + } + } +`; export const initIngestionValue = (ingestionTaxiiData: IngestionTaxiiEditionFragment_ingestionTaxii$data) => { return { ...{ @@ -46,6 +61,7 @@ export const initIngestionValue = (ingestionTaxiiData: IngestionTaxiiEditionFrag user_id: convertUser(ingestionTaxiiData, 'user'), added_after_start: ingestionTaxiiData.added_after_start, confidence_to_score: ingestionTaxiiData.confidence_to_score, + automatic_user: true, }, ...(ingestionTaxiiData.authentication_type === BEARER_AUTH ? { @@ -401,11 +417,22 @@ const IngestionTaxiiEdition: FunctionComponent = ({ )} + {ingestionTaxiiData.user?.name === 'SYSTEM' + && ( + setFieldValue('user_id', `[F] ${values.name}`)} + dataId={ingestionTaxiiData.id} + mutation={ingestionTaxiiEditionUserHandlingPatch} + /> + ) + } = ({ paginationOptions }) => { + const { fileId, serviceInstanceId } = useParams(); + const navigate = useNavigate(); + const inputFileRef = useRef(null); + const [open, setOpen] = useState(false); + const [ingestTaxiiData, setIngestTaxiiData] = useState(undefined); + const { t_i18n } = useFormatter(); + + const handleFileImport = async (file: File) => { + if (!file) return; + try { + const data = await fetchQuery(taxiiFeedImportQuery, { file }).toPromise(); + const { taxiiFeedAddInputFromImport } = data as IngestionTaxiiImportQuery$data; + setIngestTaxiiData(taxiiFeedAddInputFromImport); + setOpen(true); + if (inputFileRef.current) { + inputFileRef.current.value = ''; + } + } catch (e) { + const { errors } = (e as unknown as RelayError).res; + MESSAGING$.notifyError(errors.at(0)?.message); + } + }; + + const fileImport = (event: BaseSyntheticEvent) => { + const file = event.target.files[0]; + handleFileImport(file); + }; + + const handleDownloadError = () => { + navigate('/dashboard/data/ingestion/taxii'); + MESSAGING$.notifyError(t_i18n('An error occurred while importing Taxii Feed configuration.')); + }; + + const { dialogConnectivityLostStatus } = useXtmHubDownloadDocument({ + serviceInstanceId, + fileId, + onSuccess: handleFileImport, + onError: handleDownloadError, + }); + + const handleConfirm = () => { + navigate('/dashboard/settings/experience'); + }; + + const handleCancel = () => { + navigate('/dashboard/workspaces/dashboards'); + }; + + return ( + <> + + inputFileRef?.current?.click()} + > + + + + setOpen(false)} + ingestionTaxiiData={ingestTaxiiData} + paginationOptions={paginationOptions} + triggerButton={false} + drawerSettings={{ + title: t_i18n('Import a Taxii Feed'), + button: t_i18n('Create'), + }} + /> + + ); +}; + +export default IngestionTaxiiImport; diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx index 0e4725c3153d..f3c69b840921 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FunctionComponent, Suspense, useState } from 'react'; +import React, { Dispatch, FunctionComponent, Suspense, UIEvent, useState } from 'react'; import { graphql, useQueryLoader } from 'react-relay'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; @@ -20,6 +20,10 @@ import useApiMutation from '../../../../utils/hooks/useApiMutation'; import Transition from '../../../../components/Transition'; import DeleteDialog from '../../../../components/DeleteDialog'; import useDeletion from '../../../../utils/hooks/useDeletion'; +import { fetchQuery } from '../../../../relay/environment'; +import fileDownload from 'js-file-download'; +import stopEvent from '../../../../utils/domEvent'; +import { IngestionTaxiiPopoverExportQuery$data } from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiPopoverExportQuery.graphql'; const ingestionTaxiiPopoverDeletionMutation = graphql` mutation IngestionTaxiiPopoverDeletionMutation($id: ID!) { @@ -35,6 +39,14 @@ const ingestionTaxiiPopoverResetStateMutation = graphql` } `; +const ingestionTaxiiPopoverExportQuery = graphql` + query IngestionTaxiiPopoverExportQuery($id: String!) { + ingestionTaxii(id: $id) { + name + toConfigurationExport + } + } +`; interface IngestionTaxiiPopoverProps { ingestionTaxiiId: string; running?: boolean | null; @@ -170,6 +182,25 @@ const IngestionTaxiiPopover: FunctionComponent = ({ }); }; + const exportTaxiiFeed = async () => { + const { ingestionTaxii } = await fetchQuery( + ingestionTaxiiPopoverExportQuery, + { id: ingestionTaxiiId }, + ).toPromise() as IngestionTaxiiPopoverExportQuery$data; + + if (ingestionTaxii) { + const blob = new Blob([ingestionTaxii.toConfigurationExport], { type: 'text/json' }); + const [day, month, year] = new Date().toLocaleDateString('fr-FR').split('/'); + const fileName = `${year}${month}${day}_taxiiFeed_${ingestionTaxii.name}.json`; + fileDownload(blob, fileName); + } + }; + const handleExport = async (e: UIEvent) => { + stopEvent(e); + setAnchorEl(undefined); + await exportTaxiiFeed(); + }; + return (
= ({ {t_i18n('Update')} + + {t_i18n('Export')} + {t_i18n('Delete')} diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx index 2127e7eed253..86d3ecf5a60c 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx @@ -55,6 +55,8 @@ export type PlaybookFlowFormData time?: string; period?: string; day?: string; + // Component: Container wrapper + all?: boolean; }; interface PlaybookFlowFormProps { @@ -298,15 +300,21 @@ const PlaybookFlowForm = ({ } if (property.type === 'boolean') { let helperText = ''; + let disabled = false; if (propName === 'create_rel') { helperText = t_i18n('If both entities are of interest for selected PIR, then the target is kept'); } + // excludeMainElement depends on 'all' being enabled + if (propName === 'excludeMainElement') { + disabled = !values.all; + } return ( ); } diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx index 47357438202b..62b8cf3227e0 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx @@ -21,12 +21,14 @@ interface PlaybookFlowFieldBooleanProps { name: string; label: string; helperText?: string; + disabled?: boolean; } const PlaybookFlowFieldBoolean = ({ name, label, helperText, + disabled, }: PlaybookFlowFieldBooleanProps) => { return ( ); }; diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionAlerts.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionAlerts.tsx deleted file mode 100644 index 994edf87c4f2..000000000000 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionAlerts.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright (c) 2021-2025 Filigran SAS - -This file is part of the OpenCTI Enterprise Edition ("EE") and is -licensed under the OpenCTI Enterprise Edition License (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -https://github.com/OpenCTI-Platform/opencti/blob/master/LICENSE - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -*/ - -import { Alert } from '@mui/material'; -import { useFormatter } from '../../../../../../../components/i18n'; -import { PlaybookUpdateAction } from './playbookAction-types'; - -interface ActionAlertsProps { - action: PlaybookUpdateAction; -} - -const PlaybookActionAlerts = ({ action }: ActionAlertsProps) => { - const { t_i18n } = useFormatter(); - - return ( - <> - {(action.op === 'replace' && ['objectMarking', 'objectLabel', 'objectAssignee', 'objectParticipant'].includes(action.attribute ?? '')) && ( - - {t_i18n('Replace operation will effectively replace this field values added in the context of this playbook such as enrichment or other knowledge manipulations but it will only append them if values are already written in the platform.')} - - )} - {(action.op === 'replace' && action.attribute === 'createdBy') && ( - - {t_i18n('Replace operation will effectively replace the author if the confidence level of the entity with the new author is superior to the one of the entity with the old author.')} - - )} - {(action.op === 'remove') && ( - - {t_i18n('Remove operation will only apply on field values added in the context of this playbook such as enrichment or other knowledge manipulations but not if values are already written in the platform.')} - - )} - - ); -}; - -export default PlaybookActionAlerts; diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx index 3c6954375b09..d521a8374bfb 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx @@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import { Field, useFormikContext } from 'formik'; import SwitchField from '../../../../../../../components/fields/SwitchField'; -import { FieldOption, KillChainPhaseFieldOption } from '../../../../../../../utils/field'; +import { FieldOption } from '../../../../../../../utils/field'; import { isEmptyField } from '../../../../../../../utils/utils'; import CreatedByField from '../../../../../common/form/CreatedByField'; import KillChainPhasesField from '../../../../../common/form/KillChainPhasesField'; @@ -250,7 +250,7 @@ const PlaybookActionValueField = ({ return ( { + onChange={(_, killChainPhases) => { setFieldValue( valueName, killChainPhases.map((kcp) => ({ diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookFlowFieldActions.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookFlowFieldActions.tsx index 6299d50f26f8..b1a417c93318 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookFlowFieldActions.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookFlowFieldActions.tsx @@ -24,7 +24,6 @@ import { isEmptyField } from '../../../../../../../utils/utils'; import type { Theme } from '../../../../../../../components/Theme'; import SelectField from '../../../../../../../components/fields/SelectField'; import { attributesMultiple, PlaybookUpdateAction, PlaybookUpdateActionsForm } from './playbookAction-types'; -import PlaybookActionAlerts from './PlaybookActionAlerts'; import useActionFieldOptions from './useActionFieldOptions'; import PlaybookActionValueField from './PlaybookActionValueField'; @@ -83,7 +82,6 @@ const PlaybookFlowFieldActions = ({ return (
-
({ - buttons: { - width: '100%', - marginTop: 20, - textAlign: 'right', - }, - button: { - marginLeft: theme.spacing(2), - }, - alert: { - width: '100%', - marginTop: 20, - }, - message: { - width: '100%', - overflow: 'hidden', - }, -})); +import IngestionCreationUserHandling from '../../../../private/components/data/IngestionCreationUserHandling'; +import { PaginationOptions } from '../../../../components/list_lines'; +import { SyncImportQuery$data } from '@components/data/__generated__/SyncImportQuery.graphql'; +import useApiMutation from '../../../../utils/hooks/useApiMutation'; +import { FormikConfig, FormikHelpers } from 'formik/dist/types'; +import { SyncCreationCheckMutation$data } from '@components/data/sync/__generated__/SyncCreationCheckMutation.graphql'; +import { SyncCreationStreamCollectionQuery$data } from '@components/data/sync/__generated__/SyncCreationStreamCollectionQuery.graphql'; +import { RelayError } from '../../../../relay/relayTypes'; const syncCreationMutation = graphql` mutation SyncCreationMutation($input: SynchronizerAddInput!) { @@ -66,19 +51,22 @@ export const syncCheckMutation = graphql` } `; -const syncCreationValidation = (t) => Yup.object().shape({ - name: Yup.string().required(t('This field is required')), - uri: Yup.string().required(t('This field is required')), - token: Yup.string(), - stream_id: Yup.string().required(t('This field is required')), - current_state_date: Yup.date() - .nullable() - .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - listen_deletion: Yup.bool(), - no_dependencies: Yup.bool(), - ssl_verify: Yup.bool(), - synchronized: Yup.bool(), -}); +const syncCreationValidation = () => { + const { t_i18n } = useFormatter(); + Yup.object().shape({ + name: Yup.string().required(t_i18n('This field is required')), + uri: Yup.string().required(t_i18n('This field is required')), + token: Yup.string(), + stream_id: Yup.string().required(t_i18n('This field is required')), + current_state_date: Yup.date() + .nullable() + .typeError(t_i18n('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + listen_deletion: Yup.bool(), + no_dependencies: Yup.bool(), + ssl_verify: Yup.bool(), + synchronized: Yup.bool(), + }); +}; export const syncStreamCollectionQuery = graphql` query SyncCreationStreamCollectionQuery( @@ -97,26 +85,76 @@ export const syncStreamCollectionQuery = graphql` } `; -const CreateSynchronizerControlledDial = (props) => ( +interface SynchronizerAddInput { + name: string; + uri: string; + stream_id: string; + token: string; + current_state_date?: Date | null; + listen_deletion?: boolean; + ssl_verify?: boolean; + no_dependencies?: boolean; + synchronized?: boolean; + user_id: string | FieldOption; + automatic_user?: boolean; + confidence_level?: string; +} +type StreamOption = { + value: string; + label: string; + id: string; + name: string; + description?: string | null; + filters?: string | null; +}; + +const CreateSynchronizerControlledDial = (props: DrawerControlledDialProps) => ( ); -const SyncCreation = ({ paginationOptions }) => { +interface SyncCreationProps { + paginationOptions?: PaginationOptions; + handleClose?: () => void; + ingestionSynchronizerData?: SyncImportQuery$data['synchronizerAddInputFromImport']; + triggerButton?: boolean; + open?: boolean; + drawerSettings?: { + title: string; + button: string; + }; +} + +const SyncCreation: FunctionComponent = ({ + paginationOptions, + handleClose, + ingestionSynchronizerData, + triggerButton = false, + open = false, + drawerSettings, +}) => { const { t_i18n } = useFormatter(); - const classes = useStyles(); const [verified, setVerified] = useState(false); - const [streams, setStreams] = useState([]); + const [streams, setStreams] = useState([]); - const handleVerify = (values, setErrors) => { - const input = { ...values, user_id: values.user_id?.value }; - commitMutation({ - mutation: syncCheckMutation, + const [commitVerify] = useApiMutation(syncCheckMutation); + + const handleVerify = (values: SynchronizerAddInput, setErrors: FormikHelpers['setErrors']) => { + const userId + = typeof values.user_id === 'object' + ? values.user_id?.value + : values.user_id; + const input = { ...values, user_id: userId, + automatic_user: values.automatic_user ?? true, + ...((values.automatic_user !== false) && { confidence_level: Number(values.confidence_level) }), + }; + commitVerify({ variables: { input }, - onCompleted: (data) => { + onCompleted: (response) => { + const data = response as SyncCreationCheckMutation$data; if (data && data.synchronizerTest === 'Connection success') { MESSAGING$.notifySuccess(t_i18n('Connection successfully verified')); setVerified(true); @@ -129,10 +167,18 @@ const SyncCreation = ({ paginationOptions }) => { }); }; - const onSubmit = (values, { setSubmitting, setErrors, resetForm }) => { - const input = { ...values, user_id: values.user_id?.value }; - commitMutation({ - mutation: syncCreationMutation, + const [commitCreation] = useApiMutation(syncCreationMutation); + + const onSubmit: FormikConfig['onSubmit'] = (values, { setSubmitting, setErrors, resetForm }) => { + const userId + = typeof values.user_id === 'object' + ? values.user_id?.value + : values.user_id; + const input = { ...values, user_id: userId, + automatic_user: values.automatic_user ?? true, + ...((values.automatic_user !== false) && { confidence_level: Number(values.confidence_level) }), + }; + commitCreation({ variables: { input }, updater: (store) => { insertNode( @@ -146,7 +192,6 @@ const SyncCreation = ({ paginationOptions }) => { handleErrorInForm(error, setErrors); setSubmitting(false); }, - setSubmitting, onCompleted: () => { setSubmitting(false); setVerified(false); @@ -156,18 +201,20 @@ const SyncCreation = ({ paginationOptions }) => { }); }; const handleGetStreams = ( - { uri, token, ssl_verify }, - setErrors, - currentErrors, + values: SynchronizerAddInput, + setErrors: FormikHelpers['setErrors'], + currentErrors: FormikErrors, ) => { - const args = { uri, token, ssl_verify: ssl_verify ?? false }; + const args = { uri: values.uri, token: values.token, ssl_verify: values.ssl_verify ?? false }; fetchQuery(syncStreamCollectionQuery, args) .toPromise() .then((result) => { + const data = result as SyncCreationStreamCollectionQuery$data; + const streamsData = data.synchronizerFetch ?? []; const resultStreams = [ - ...result.synchronizerFetch.map((s) => ({ - value: s.id, - label: s.name, + ...streamsData.map((s) => ({ + value: s?.id, + label: s?.name, ...s, })), ]; @@ -178,12 +225,12 @@ const SyncCreation = ({ paginationOptions }) => { }); } else { setErrors(R.dissoc('uri', currentErrors)); - setStreams(resultStreams); + setStreams(resultStreams as StreamOption[]); } }) - .catch((e) => { + .catch((e: RelayError) => { const errors = e.res.errors.map((err) => ({ - [err.data.field]: err.data.message, + [err.data?.field ?? 'unknownField']: err.data?.message, })); const formError = R.mergeAll(errors); setErrors({ ...currentErrors, ...formError }); @@ -193,23 +240,27 @@ const SyncCreation = ({ paginationOptions }) => { return ( {({ onClose }) => ( - initialValues={{ - name: '', - uri: '', + name: ingestionSynchronizerData?.name || '', + uri: ingestionSynchronizerData?.uri || '', token: '', - current_state_date: dayStartDate(), - stream_id: '', - no_dependencies: false, - listen_deletion: true, - ssl_verify: false, - synchronized: false, + current_state_date: ingestionSynchronizerData?.current_state_date ?? dayStartDate(), + stream_id: ingestionSynchronizerData?.stream_id || '', + no_dependencies: ingestionSynchronizerData?.no_dependencies ?? false, + listen_deletion: ingestionSynchronizerData?.listen_deletion ?? true, + ssl_verify: ingestionSynchronizerData?.ssl_verify ?? false, + synchronized: ingestionSynchronizerData?.synchronized ?? false, + user_id: '', + automatic_user: true, }} - validationSchema={syncCreationValidation(t_i18n)} + validationSchema={syncCreationValidation()} onSubmit={onSubmit} onReset={onClose} > @@ -233,10 +284,11 @@ const SyncCreation = ({ paginationOptions }) => { /> {t_i18n('Remote OpenCTI configuration')} { label={t_i18n('Remote OpenCTI stream ID')} inputProps={{ name: 'stream_id', id: 'stream_id' }} containerstyle={fieldSpacingContainerStyle} - renderValue={(value) => streams.filter((stream) => stream.value === value).at(0) - .name - } + renderValue={(value: string | undefined) => streams.filter((stream) => stream.value === value).at(0)?.name} > {streams.map( ({ value, label, name, description, filters }) => { @@ -315,7 +365,12 @@ const SyncCreation = ({ paginationOptions }) => { )} )} -
+
{streams.length === 0 && ( @@ -338,18 +395,18 @@ const SyncCreation = ({ paginationOptions }) => { setStreams([]); }} disabled={isSubmitting} - classes={{ root: classes.button }} + style={{ + marginLeft: 10, + }} > {t_i18n('Reset')} )}
- {
{t_i18n('Use these options if you know what you are doing')} @@ -426,12 +481,19 @@ const SyncCreation = ({ paginationOptions }) => {
-
+
@@ -440,7 +502,9 @@ const SyncCreation = ({ paginationOptions }) => { color="secondary" onClick={() => handleVerify(values, setErrors)} disabled={!values.stream_id || isSubmitting} - classes={{ root: classes.button }} + style={{ + marginLeft: 10, + }} > {t_i18n('Verify')} @@ -449,9 +513,11 @@ const SyncCreation = ({ paginationOptions }) => { color="secondary" onClick={submitForm} disabled={!values.stream_id || !verified || isSubmitting} - classes={{ root: classes.button }} + style={{ + marginLeft: 10, + }} > - {t_i18n('Create')} + {drawerSettings?.button ?? t_i18n('Create')}
diff --git a/opencti-platform/opencti-front/src/private/components/data/sync/SyncEdition.jsx b/opencti-platform/opencti-front/src/private/components/data/sync/SyncEdition.jsx index eae72dc49b0c..62cf23af5c55 100644 --- a/opencti-platform/opencti-front/src/private/components/data/sync/SyncEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/sync/SyncEdition.jsx @@ -24,6 +24,7 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import { Accordion, AccordionSummary } from '../../../../components/Accordion'; import PasswordTextField from '../../../../components/PasswordTextField'; import { extractToken } from '../../../../utils/ingestionAuthentificationUtils'; +import IngestionEditionUserHandling from '../../../../private/components/data/IngestionEditionUserHandling'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. @@ -55,6 +56,20 @@ const syncMutationFieldPatch = graphql` } `; +export const syncEditionUserHandlingPatch = graphql` + mutation SyncEditionUserHandlingMutation($id: ID!, $input: SynchronizerAddAutoUserInput!) { + synchronizerAddAutoUser(id: $id, input: $input) { + id + name + user { + id + entity_type + name + } + } + } +`; + const syncValidation = (t) => Yup.object().shape({ name: Yup.string().trim().required(t('This field is required')), uri: Yup.string().trim().required(t('This field is required')), @@ -88,8 +103,9 @@ const SyncEditionContainer = ({ synchronizer }) => { no_dependencies: synchronizer.no_dependencies, ssl_verify: synchronizer.ssl_verify, synchronized: synchronizer.synchronized, - current_state_date: buildDate(synchronizer.current_state_date), + current_state_date: buildDate(synchronizer?.current_state_date), user_id: relatedUser, + automatic_user: true, }; const isStreamAccessible = isNotEmptyField( @@ -157,7 +173,7 @@ const SyncEditionContainer = ({ synchronizer }) => { initialValues={initialValues} validationSchema={syncValidation(t_i18n)} > - {({ values }) => ( + {({ values, setFieldValue }) => (
{ + {synchronizer.user?.name === 'SYSTEM' + && ( + setFieldValue('user_id', `[S] ${values.name}`)} + dataId={synchronizer.id} + mutation={syncEditionUserHandlingPatch} + /> + ) + } ({ container: { @@ -101,6 +102,15 @@ const syncEditionQuery = graphql` } `; +const syncPopoverExportQuery = graphql` + query SyncPopoverExportQuery($id: String!) { + synchronizer(id: $id) { + name + toConfigurationExport + } + } +`; + class SyncPopover extends Component { constructor(props) { super(props); @@ -210,6 +220,42 @@ class SyncPopover extends Component { }); } + async exportSync() { + const { syncId } = this.props; + + const data = await fetchQuery(environment, + syncPopoverExportQuery, + { id: syncId }, + ).toPromise(); + + if (data && data.synchronizer) { + const { synchronizer } = data; + + const blob = new Blob( + [synchronizer.toConfigurationExport], + { type: 'application/json' }, + ); + + const [day, month, year] = new Date() + .toLocaleDateString('fr-FR') + .split('/'); + + const fileName = `${year}${month}${day}_synchronizer_${synchronizer.name}.json`; + + fileDownload(blob, fileName); + } + } + + async handleExport(event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + this.setState({ anchorEl: null }); + await this.exportSync(); + } + render() { const { classes, t, syncId, running } = this.props; return ( @@ -241,6 +287,9 @@ class SyncPopover extends Component { {t('Update')} + + {t('Export')} + {t('Delete')} diff --git a/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.tsx b/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.tsx index 133e988084c1..189f9b71838f 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.tsx @@ -68,7 +68,7 @@ interface EventAddInput { } interface EventFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: EventCreationMutation['response']['eventAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: FieldOption; @@ -122,9 +122,9 @@ export const EventCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'eventAdd'); + updater(store, 'eventAdd', response?.eventAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx index 1da484566bb6..c0be17a12d0b 100644 --- a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx @@ -9,6 +9,7 @@ import { FormikConfig } from 'formik/dist/types'; import { RecordSourceSelectorProxy } from 'relay-runtime'; import Drawer, { DrawerControlledDialProps } from '@components/common/drawer/Drawer'; import { IncidentsLinesQuery$variables } from '@components/events/incidents/__generated__/IncidentsLinesQuery.graphql'; +import { IncidentCreationMutation, IncidentCreationMutation$data } from './__generated__/IncidentCreationMutation.graphql'; import { useFormatter } from '../../../../components/i18n'; import { handleErrorInForm } from '../../../../relay/environment'; import TextField from '../../../../components/TextField'; @@ -77,7 +78,7 @@ interface IncidentAddInput { } interface IncidentCreationProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: IncidentCreationMutation['response']['incidentAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: FieldOption; @@ -143,9 +144,10 @@ export const IncidentCreationForm: FunctionComponent = ({ variables: { input, }, - updater: (store) => { + updater: (store, response) => { if (updater) { - updater(store, 'incidentAdd'); + const data = response as IncidentCreationMutation$data; + updater(store, 'incidentAdd', data?.incidentAdd); } }, onError: (error) => { diff --git a/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataCreation.tsx b/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataCreation.tsx index 20154f6d3642..d1cc922ba6de 100644 --- a/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataCreation.tsx @@ -181,8 +181,6 @@ export const ObservedDataCreationForm: FunctionComponent< name="objects" style={fieldSpacingContainerStyle} required={(mandatoryAttributes.includes('objects'))} - setFieldValue={setFieldValue} - values={values.objects} /> void; + updater: (store: RecordSourceSelectorProxy, key: string, response: CityCreationMutation['response']['cityAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -115,9 +115,9 @@ export const CityCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'cityAdd'); + updater(store, 'cityAdd', response?.cityAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx b/opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx deleted file mode 100644 index 207af8dbfc6a..000000000000 --- a/opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { Field } from 'formik'; -import { assoc, compose, map, pipe, prop, sortBy, toLower } from 'ramda'; -import withStyles from '@mui/styles/withStyles'; -import MenuItem from '@mui/material/MenuItem'; -import inject18n from '../../../components/i18n'; -import SelectField from '../../../components/fields/SelectField'; -import { QueryRenderer } from '../../../relay/environment'; -import { stixCyberObservablesLinesSubTypesQuery } from './stix_cyber_observables/StixCyberObservablesLines'; - -const styles = () => ({ - container: { - margin: 0, - }, -}); - -class TypesField extends Component { - render() { - const { t, name, label, containerstyle } = this.props; - return ( - { - if (props && props.subTypes) { - const subTypesEdges = props.subTypes.edges; - const sortByLabel = sortBy(compose(toLower, prop('tlabel'))); - const translatedOrderedList = pipe( - map((n) => n.node), - map((n) => assoc('tlabel', t(`entity_${n.label}`), n)), - sortByLabel, - )(subTypesEdges); - return ( - - {translatedOrderedList.map((subType) => ( - - {subType.tlabel} - - ))} - - ); - } - return
; - }} - /> - ); - } -} - -TypesField.propTypes = { - classes: PropTypes.object, - t: PropTypes.func, -}; - -export default compose(inject18n, withStyles(styles))(TypesField); diff --git a/opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx b/opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx new file mode 100644 index 000000000000..282fc37f4fef --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx @@ -0,0 +1,104 @@ +import React, { Suspense } from 'react'; +import { Field } from 'formik'; +import MenuItem from '@mui/material/MenuItem'; +import { useFormatter } from '../../../components/i18n'; +import SelectField from '../../../components/fields/SelectField'; +import { stixCyberObservablesLinesSubTypesQuery } from './stix_cyber_observables/StixCyberObservablesLines'; +import useQueryLoading from 'src/utils/hooks/useQueryLoading'; +import { PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import { StixCyberObservablesLinesSubTypesQuery } from '@components/observations/stix_cyber_observables/__generated__/StixCyberObservablesLinesSubTypesQuery.graphql'; + +interface TypesFieldComponentProps { + queryRef: PreloadedQuery; + name: string; + label?: string; + required?: boolean; + containerstyle?: Record; + multiple?: boolean; + onChange?: (name: string, value: string | string[]) => void; +} +interface TLabel { + tlabel: string; +} + +const TypesFieldComponent = ({ + name, + label, + required = false, + containerstyle, + queryRef, +}: TypesFieldComponentProps) => { + const { t_i18n } = useFormatter(); + const { subTypes } = usePreloadedQuery(stixCyberObservablesLinesSubTypesQuery, queryRef); + + if (subTypes) { + const subTypesEdges = subTypes.edges; + const sortByLabel = (arr: T[]): T[] => + [...arr].sort((a, b) => { + const labelA = String(a.tlabel).toLowerCase(); + const labelB = String(b.tlabel).toLowerCase(); + return labelA.localeCompare(labelB); + }); + + const translatedOrderedList = sortByLabel( + subTypesEdges + .map((n) => n.node) + .map((n) => ({ + ...n, + tlabel: t_i18n(`entity_${n.label}`), + })), + ); + return ( + + {translatedOrderedList.map((subType) => ( + + {subType.tlabel} + + ))} + + ); + } +}; + +type TypesFieldProps = Omit; + +const TypesField = (props: TypesFieldProps) => { + const { name, label, containerstyle, required } = props; + + const queryRef = useQueryLoading( + stixCyberObservablesLinesSubTypesQuery, + { type: 'Stix-Cyber-Observable' }, + ); + const FallbackSelect = ( + + ); + + return ( + + {queryRef && ( + + )} + + ); +}; + +export default TypesField; diff --git a/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorCreation.tsx b/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorCreation.tsx index eb11297e1811..b77be24f2067 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorCreation.tsx @@ -101,7 +101,7 @@ interface IndicatorAddInput { } interface IndicatorFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: IndicatorCreationMutation['response']['indicatorAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -185,9 +185,9 @@ export const IndicatorCreationForm: FunctionComponent = ({ variables: { input, }, - updater: (store) => { + updater: (store, response) => { if (updater) { - updater(store, 'indicatorAdd'); + updater(store, 'indicatorAdd', response?.indicatorAdd); } }, onError: (error) => { diff --git a/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEntityLine.jsx b/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEntityLine.jsx index e9a66c3c8504..bb7420ef6b74 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEntityLine.jsx +++ b/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEntityLine.jsx @@ -63,6 +63,18 @@ class IndicatorEntityLineComponent extends Component { const element = node.to?.id === entityId ? node.from : node.to; const restricted = isEmptyField(element); const link = `${entityLink}/relations/${node.id}`; + + const isRelationship = t(`relationship_${element.entity_type}`) !== `relationship_${element.entity_type}`; + + const displayName = !restricted + ? isRelationship + ? element.representative?.main + ?? `${element.from?.name ?? element.from?.observable_value ?? '-'} + ${String.fromCharCode(8594)} + ${element.to?.name ?? element.to?.observable_value ?? '-'}` + : element.name || element.observable_value + : t('Restricted'); + return ( - {/* eslint-disable-next-line no-nested-ternary */} - {!restricted - ? element.entity_type === 'stix_relation' - || element.entity_type === 'stix-relation' - ? `${element.from.name} ${String.fromCharCode(8594)} ${ - element.to.name || element.to.observable_value - }` - : element.name || element.observable_value - : t('Restricted')} + {displayName}
BULK_OBSERVABLES.find(({ type: obsType }) => obsType === status.type), [status]); + // Store the latest created observable for callback + const lastCreatedObservableRef = React.useRef(null); + useEffect(() => { setBulkSelectedKey(bulkConf?.keys.length === 1 ? bulkConf.keys[0] : null); }, [bulkConf]); @@ -306,6 +309,7 @@ const StixCyberObservableCreation = ({ undefined, { successMessage: `${t_i18n('entity_Observable')} ${t_i18n('successfully created')}` }, ); + const { bulkCommit, bulkCount, @@ -315,13 +319,17 @@ const StixCyberObservableCreation = ({ } = useBulkCommit({ type: 'observables', commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { insertNode( store, paginationKey, paginationOptions, 'stixCyberObservableAdd', ); + // Store the created observable for callback + if (response?.stixCyberObservableAdd) { + lastCreatedObservableRef.current = response.stixCyberObservableAdd; + } }, }); @@ -441,7 +449,11 @@ const StixCyberObservableCreation = ({ resetForm(); localHandleClose(); } - if (onCompleted) onCompleted(); + // Pass the created observable to the callback + if (onCompleted) { + onCompleted(lastCreatedObservableRef.current); + lastCreatedObservableRef.current = null; + } }, }); }; diff --git a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx index 5b9a993e20c8..65b043acc6e4 100644 --- a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx +++ b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx @@ -34,12 +34,12 @@ interface PirKnowledgeProps { const PirKnowledge = ({ data }: PirKnowledgeProps) => { const pir = useFragment(knowledgeFragment, data); const pirId = pir.id; - const LOCAL_STORAGE_KEY = `PirSourcesFlaggedList-${pirId}`; + const LOCAL_STORAGE_KEY = `Pir-SourcesFlaggedList-${pirId}`; const initialValues = { filters: { ...emptyFilterGroup, - filters: useGetDefaultFilterObject([`pir_score.${pirId}`, `last_pir_score_date.${pirId}`], ['Stix-Domain-Object']), + filters: useGetDefaultFilterObject(['pir_score', 'last_pir_score_date'], ['Stix-Domain-Object']), }, searchTerm: '', sortBy: 'pir_score', diff --git a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx index cdc0451ff06e..b01d1fb136a7 100644 --- a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx +++ b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx @@ -23,7 +23,7 @@ import { PirKnowledgeEntitiesSourcesFlaggedListQuery$variables, } from './__generated__/PirKnowledgeEntitiesSourcesFlaggedListQuery.graphql'; import { PirKnowledgeEntities_SourceFlaggedFragment$data } from './__generated__/PirKnowledgeEntities_SourceFlaggedFragment.graphql'; -import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; +import { formatFiltersInPirContext, isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; import { PaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage'; import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import { DataTableProps } from '../../../../components/dataGrid/dataTableTypes'; @@ -176,7 +176,7 @@ const PirKnowledgeEntities = ({ pirId, localStorage, initialValues, additionalHe }, ], filterGroups: filters && isFilterGroupNotEmpty(filters) - ? [filters] + ? [formatFiltersInPirContext(filters, pirId)] : [], }; const queryPaginationOptions = { @@ -262,7 +262,7 @@ const PirKnowledgeEntities = ({ pirId, localStorage, initialValues, additionalHe return computeLink(e); }} additionalHeaderButtons={additionalHeaderButtons} - additionalFilterKeys={[`pir_score.${pirId}`, `last_pir_score_date.${pirId}`]} + additionalFilterKeys={['pir_score', 'last_pir_score_date']} /> )} diff --git a/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx b/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx index 5cd862ae86b8..edf0a8e5abb8 100644 --- a/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx +++ b/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx @@ -744,10 +744,10 @@ const ProfileOverview = createFragmentContainer(ProfileOverviewComponent, { submenu_show_icons submenu_auto_collapse monochrome_labels - # personal_notifiers { - # id - # name - # } + personal_notifiers { + id + name + } objectOrganization { edges { node { diff --git a/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx b/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx index 66dcb7184863..5b43e0cecf11 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx @@ -69,6 +69,7 @@ const PoliciesFragment = graphql` } otp_mandatory view_all_users + platform_session_max_concurrent } `; @@ -106,6 +107,7 @@ const policiesValidation = () => Yup.object().shape({ platform_consent_confirm_text: Yup.string().nullable(), platform_banner_level: Yup.string().nullable(), platform_banner_text: Yup.string().nullable(), + platform_session_max_concurrent: Yup.number().nullable(), }); interface PoliciesComponentProps { @@ -177,6 +179,7 @@ const PoliciesComponent: FunctionComponent = ({ platform_banner_level: settings.platform_banner_level, platform_banner_text: settings.platform_banner_text, otp_mandatory: settings.otp_mandatory, + platform_session_max_concurrent: settings.platform_session_max_concurrent, default_group_for_ingestion_users: null, view_all_users: settings.view_all_users ?? false, }; @@ -572,7 +575,28 @@ const PoliciesComponent: FunctionComponent = ({ className="paper-for-grid" variant="outlined" > - + handleSubmitField(name, value)} + tooltip={t_i18n( + 'When enforcing 2FA authentication, all users will be asked to enable 2FA to be able to login in the platform.', + )} + /> + handleSubmitField(name, value !== '' ? value : '0')} + /> + {authProviders.map((provider) => ( @@ -590,17 +614,6 @@ const PoliciesComponent: FunctionComponent = ({ ))} - handleSubmitField(name, value)} - tooltip={t_i18n( - 'When enforcing 2FA authentication, all users will be asked to enable 2FA to be able to login in the platform.', - )} - /> diff --git a/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx b/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx index 13277a4f856d..e268eb60dbed 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx @@ -211,6 +211,23 @@ const SettingsComponent = ({ queryRef }: SettingsComponentProps) => { setTitle(t_i18n('Parameters | Settings')); + // generate AI Powered label and tooltip + let aiPoweredLabel; + let aiPoweredTooltip; + if (!isEnterpriseEditionValid) { + aiPoweredLabel = t_i18n('Disabled'); + aiPoweredTooltip = t_i18n('You should activate EE to use this feature'); + } else if (!settings.platform_ai_enabled) { + aiPoweredLabel = t_i18n('Disabled'); + aiPoweredTooltip = t_i18n('AI is not enabled'); + } else if (settings.platform_ai_has_token) { + aiPoweredLabel = settings.platform_ai_type; + aiPoweredTooltip = `${settings.platform_ai_type} - ${settings.platform_ai_model}`; + } else { + aiPoweredLabel = `${settings.platform_ai_type} - ${t_i18n('Missing token')}`; + aiPoweredTooltip = t_i18n('The token is missing in your platform configuration, please ask your Filigran representative to provide you with it or with on-premise deployment instructions. Your can open a support ticket to do so.'); + }; + const settingsValidation = () => Yup.object().shape({ platform_title: Yup.string().required(t_i18n('This field is required')), platform_favicon: Yup.string().nullable(), @@ -688,16 +705,18 @@ const SettingsComponent = ({ queryRef }: SettingsComponentProps) => { + {t_i18n('AI Powered')} + + + )} /> diff --git a/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx b/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx index 86d83447344e..f71db95c786a 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx @@ -116,9 +116,10 @@ const ConfigurationComponent: FunctionComponent< ); const currentListeners = (settings.activity_listeners ?? []).map((a) => a.id); const onChangeData = (resetForm: () => void) => { - return (name: string, data: FieldOption) => { - if (!currentListeners.includes(data.value)) { - const value = R.uniq([...currentListeners, data.value]); + return (name: string, data: FieldOption | null) => { + const val = data?.value ?? ''; + if (!currentListeners.includes(val)) { + const value = R.uniq([...currentListeners, val]); commit({ variables: { id: settings?.id, diff --git a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx index 861e00bf4a3d..c4d53a6ceb60 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx @@ -264,6 +264,7 @@ const GroupEditionOverview = createFragmentContainer( name authorizedMembers { id + member_id } } ...GroupHiddenTypesField_group diff --git a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.tsx b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.tsx index fdeb06ade59b..727b82c94a70 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.tsx @@ -98,7 +98,7 @@ interface AttackPatternAddInput { } interface AttackPatternFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: AttackPatternCreationMutation['response']['attackPatternAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -152,9 +152,9 @@ export const AttackPatternCreationForm: FunctionComponent { + updater: (store, response) => { if (updater) { - updater(store, 'attackPatternAdd'); + updater(store, 'attackPatternAdd', response?.attackPatternAdd); } }, onError: (error) => { diff --git a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.tsx b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.tsx index 37a9ec65896b..18707c6d3728 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.tsx @@ -25,7 +25,11 @@ import { FieldOption, fieldSpacingContainerStyle } from '../../../../utils/field import { useDynamicSchemaCreationValidation, useIsMandatoryAttribute, yupShapeConditionalRequired } from '../../../../utils/hooks/useEntitySettings'; import { insertNode } from '../../../../utils/store'; import type { Theme } from '../../../../components/Theme'; -import { CourseOfActionCreationMutation, CourseOfActionCreationMutation$variables } from './__generated__/CourseOfActionCreationMutation.graphql'; +import { + CourseOfActionCreationMutation, + CourseOfActionCreationMutation$data, + CourseOfActionCreationMutation$variables, +} from './__generated__/CourseOfActionCreationMutation.graphql'; import useDefaultValues from '../../../../utils/hooks/useDefaultValues'; import useApiMutation from '../../../../utils/hooks/useApiMutation'; import CreateEntityControlledDial from '../../../../components/CreateEntityControlledDial'; @@ -74,7 +78,7 @@ interface CourseOfActionAddInput { } interface CourseOfActionFormProps { - updater?: (store: RecordSourceSelectorProxy, key: string) => void; + updater?: (store: RecordSourceSelectorProxy, key: string, response: CourseOfActionCreationMutation['response']['courseOfActionAdd']) => void; paginationOptions?: CoursesOfActionLinesPaginationQuery$variables; display?: boolean; contextual?: boolean; @@ -136,9 +140,10 @@ export const CourseOfActionCreationForm: FunctionComponent { + updater: (store, response) => { if (updater) { - updater(store, 'courseOfActionAdd'); + const data = response as CourseOfActionCreationMutation$data; + updater(store, 'courseOfActionAdd', data?.courseOfActionAdd); } }, onError: (error) => { diff --git a/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeCreation.tsx b/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeCreation.tsx index 9344ab63026e..4ebb3c65af88 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeCreation.tsx @@ -87,7 +87,7 @@ interface NarrativeAddInput { } interface NarrativeFormProps { - updater?: (store: RecordSourceSelectorProxy, key: string) => void; + updater?: (store: RecordSourceSelectorProxy, key: string, response: NarrativeCreationMutation['response']['narrativeAdd']) => void; onReset?: () => void; inputValue?: string; onCompleted?: () => void; @@ -135,9 +135,9 @@ export const NarrativeCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'narrativeAdd'); + updater(store, 'narrativeAdd', response?.narrativeAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.tsx b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.tsx index d7b6a8808896..a1a3f6c17714 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.tsx @@ -61,7 +61,7 @@ interface CampaignAddInput { } interface CampaignFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: CampaignCreationMutation['response']['campaignAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -110,9 +110,9 @@ export const CampaignCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'campaignAdd'); + updater(store, 'campaignAdd', response?.campaignAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.tsx b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.tsx index 125cf04c0ef5..1a81feb78766 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.tsx @@ -61,7 +61,7 @@ interface IntrusionSetAddInput { } interface IntrusionSetFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: IntrusionSetCreationMutation['response']['intrusionSetAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -113,9 +113,9 @@ export const IntrusionSetCreationForm: FunctionComponent< resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'intrusionSetAdd'); + updater(store, 'intrusionSetAdd', response?.intrusionSetAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupCreation.tsx b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupCreation.tsx index 9d72c10f7fe2..425304d1c7e0 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupCreation.tsx @@ -63,7 +63,7 @@ interface ThreatActorGroupAddInput { } interface ThreatActorGroupFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: ThreatActorGroupCreationMutation['response']['threatActorGroupAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -117,9 +117,9 @@ export const ThreatActorGroupCreationForm: FunctionComponent< resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'threatActorGroupAdd'); + updater(store, 'threatActorGroupAdd', response?.threatActorGroupAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualCreation.tsx b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualCreation.tsx index b44da793bce9..970df156c347 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualCreation.tsx @@ -114,7 +114,7 @@ interface ThreatActorIndividualAddInput { } interface ThreatActorIndividualFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: ThreatActorIndividualCreationMutation['response']['threatActorIndividualAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -217,9 +217,9 @@ export const ThreatActorIndividualCreationForm: FunctionComponent< resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'threatActorIndividualAdd'); + updater(store, 'threatActorIndividualAdd', response?.threatActorIndividualAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx b/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx index c2dcc1bb8cf4..6a9a83268f4e 100644 --- a/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx +++ b/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx @@ -34,7 +34,7 @@ const WidgetFilters: FunctionComponent = ({ perspective, typ let availableEntityTypes; let searchContext; if (perspective === 'relationships') { - searchContext = { entityTypes: ['stix-core-relationship', 'stix-sighting-relationship', 'contains'] }; + searchContext = { entityTypes: ['stix-core-relationship', 'stix-sighting-relationship', 'contains', 'object-label'] }; } else if (perspective === 'audits') { availableEntityTypes = ['History', 'Activity']; searchContext = { entityTypes: ['History'] }; diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceCreation.tsx b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceCreation.tsx index 2c3e8d949cda..59f2732cdbfa 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceCreation.tsx @@ -63,7 +63,7 @@ const WorkspaceCreation = ({ paginationOptions, type }: WorkspaceCreationProps) const inputRef = useRef(null); const { settings, isXTMHubAccessible } = useContext(UserContext); const importFromHubUrl = isNotEmptyField(settings?.platform_xtmhub_url) - ? `${settings.platform_xtmhub_url}/redirect/octi_custom_dashboards?platform_id=${settings.id}` + ? `${settings.platform_xtmhub_url}/redirect/opencti_custom_dashboards?platform_id=${settings.id}` : ''; const [commitImportMutation] = useApiMutation(importMutation); diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx index 0b3d169929e0..a86ccc83cb60 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx @@ -58,7 +58,7 @@ const dashboardFragment = graphql` } `; -const DashboardComponent = ({ data, noToolbar }) => { +const DashboardComponent = ({ data, noToolbar = false }) => { const [commitWidgetImportMutation] = useApiMutation(dashboardImportWidgetMutation); const workspace = useFragment(dashboardFragment, data); @@ -94,20 +94,21 @@ const DashboardComponent = ({ data, noToolbar }) => { return workspace.manifest && workspace.manifest.length > 0 ? deserializeDashboardManifestForFrontend(fromB64(workspace.manifest)) : { widgets: {}, config: {} }; - }, [workspace]); + }, [workspace.manifest]); // Array of all widgets, refreshed when workspace is updated. const widgetsArray = useMemo(() => { - const widgets = Object.values(manifest.widgets).map((widget) => widget); - // Sync our local layouts. + return Object.values(manifest.widgets); + }, [manifest]); + + useEffect(() => { setWidgetsLayouts( - widgets.reduce((res, widget) => { + widgetsArray.reduce((res, widget) => { res[widget.id] = widget.layout; return res; }, {}), ); - return widgets; - }, [manifest]); + }, [widgetsArray]); /** * Merge a manifest with some layouts and transform it in base64. @@ -251,17 +252,17 @@ const DashboardComponent = ({ data, noToolbar }) => { }; const onLayoutChange = (layouts) => { - if (!deleting) { - const newLayouts = layouts.reduce((res, layout) => { - res[layout.i] = layout; - return res; - }, {}); - setWidgetsLayouts(newLayouts); - // Triggering a manifest save with the same manifest. - // As this function makes a sync between manifest and local layouts - // it will make the update of layouts modification. - saveManifest(manifest, { layouts: newLayouts, noRefresh: true }); - } + if (deleting) return; + + const newLayouts = layouts.reduce((res, layout) => { + res[layout.i] = layout; + return res; + }, {}); + + if (R.equals(newLayouts, widgetsLayouts)) return; // ⛔ prevent loop + + setWidgetsLayouts(newLayouts); + saveManifest(manifest, { layouts: newLayouts, noRefresh: true }); }; const paperStyle = { diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Root.jsx b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Root.jsx deleted file mode 100644 index b386d592454b..000000000000 --- a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Root.jsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useEffect } from 'react'; -import * as PropTypes from 'prop-types'; -import { Route, Routes, useParams } from 'react-router-dom'; -import { graphql } from 'react-relay'; -import { QueryRenderer, requestSubscription } from '../../../../relay/environment'; -import Dashboard from './Dashboard'; -import Loader from '../../../../components/Loader'; -import ErrorNotFound from '../../../../components/ErrorNotFound'; - -const subscription = graphql` - subscription RootDashboardSubscription($id: ID!) { - workspace(id: $id) { - ...Dashboard_workspace - } - } -`; - -const dashboardQuery = graphql` - query RootDashboardQuery($id: String!) { - settings { - platform_banner_text - platform_banner_level - } - workspace(id: $id) { - id - name - type - ...Dashboard_workspace - } - } -`; - -const RootDashboard = () => { - const { workspaceId } = useParams(); - - useEffect(() => { - const sub = requestSubscription({ - subscription, - variables: { id: workspaceId }, - }); - - return () => { - sub.dispose(); - }; - }, [workspaceId]); - - return ( -
- { - if (props) { - if (props.workspace) { - return ( - - - )} - /> - - ); - } - return ; - } - return ; - }} - /> -
- ); -}; - -RootDashboard.propTypes = { - children: PropTypes.node, - params: PropTypes.object, -}; - -export default RootDashboard; diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Root.tsx b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Root.tsx new file mode 100644 index 000000000000..3a4ca0f1de01 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Root.tsx @@ -0,0 +1,76 @@ +import { Suspense, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import Dashboard from './Dashboard'; +import Loader from '../../../../components/Loader'; +import ErrorNotFound from '../../../../components/ErrorNotFound'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import { RootDashboardQuery } from './__generated__/RootDashboardQuery.graphql'; +import { requestSubscription } from '../../../../relay/environment'; + +const subscription = graphql` + subscription RootDashboardSubscription($id: ID!) { + workspace(id: $id) { + ...Dashboard_workspace + } + } +`; + +const dashboardQuery = graphql` + query RootDashboardQuery($id: String!) { + workspace(id: $id) { + id + ...Dashboard_workspace + } + } +`; + +interface RootDashboardComponentProps { + queryRef: PreloadedQuery; +} + +const RootDashboardComponent = ({ queryRef }: RootDashboardComponentProps) => { + const { workspace } = usePreloadedQuery(dashboardQuery, queryRef); + if (!workspace) return ; + + useEffect(() => { + const sub = requestSubscription({ + subscription, + variables: { id: workspace.id }, + }); + return () => sub.dispose(); + }, [workspace.id]); + + return ( +
+ +
+ ); +}; + +const RootDashboard = () => { + const { workspaceId } = useParams(); + if (!workspaceId) return ; + + const queryRef = useQueryLoading( + dashboardQuery, + { id: workspaceId }, + ); + + return ( + }> + {queryRef && } + + ); +}; + +export default RootDashboard; diff --git a/opencti-platform/opencti-front/src/private/components/xtm_hub/Root.tsx b/opencti-platform/opencti-front/src/private/components/xtm_hub/Root.tsx index 44e65d9a8516..3c19807d0e5f 100644 --- a/opencti-platform/opencti-front/src/private/components/xtm_hub/Root.tsx +++ b/opencti-platform/opencti-front/src/private/components/xtm_hub/Root.tsx @@ -4,6 +4,7 @@ import { boundaryWrapper } from '../Error'; const DeployCustomDashboards = lazy(() => import('./DeployCustomDashboard')); const IngestionCsv = lazy(() => import('../data/IngestionCsv')); +const IngestionTaxii = lazy(() => import('../data/IngestionTaxiis')); const IngestionCatalogConnector = lazy(() => import('../data/IngestionCatalog/IngestionCatalogConnector')); const Root = () => { @@ -23,6 +24,10 @@ const Root = () => { path="/deploy-connector/:connectorSlug" element={boundaryWrapper(IngestionCatalogConnector)} /> + ); diff --git a/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx b/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx index e4ed0af025ac..712b64662037 100644 --- a/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx +++ b/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx @@ -10,7 +10,7 @@ const StartTrialBanner = () => { if (!settings || isEmptyField(settings?.platform_xtmhub_url) || !settings.platform_demo) return <>; - const freeTrialUrl = `${settings?.platform_xtmhub_url}/redirect/free-trial`; + const freeTrialUrl = `${settings?.platform_xtmhub_url}/cybersecurity-solutions/free-trial`; const createFreeTrialUrl = `${settings?.platform_xtmhub_url}/redirect/create-free-trial`; const text = ( diff --git a/opencti-platform/opencti-front/src/relay/relayTypes.ts b/opencti-platform/opencti-front/src/relay/relayTypes.ts index b5e666ec9380..e2b63a790e17 100644 --- a/opencti-platform/opencti-front/src/relay/relayTypes.ts +++ b/opencti-platform/opencti-front/src/relay/relayTypes.ts @@ -12,6 +12,10 @@ export interface RelayError { }; stacktrace?: string[]; }; + data?: { + field?: string; + message?: string; + }; }[]; }; } diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 9e6f8e3c147d..ecd3836442c6 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -870,6 +870,7 @@ type Synchronizer { ssl_verify: Boolean synchronized: Boolean queue_messages: Int! + toConfigurationExport: String! } type SynchronizerEdge { @@ -892,7 +893,9 @@ input SynchronizerAddInput { "*Constraints:*\n* Minimal length: `2`\n" stream_id: String! - user_id: String + user_id: String! + automatic_user: Boolean + confidence_level: Int recover: DateTime current_state_date: DateTime listen_deletion: Boolean! @@ -901,6 +904,11 @@ input SynchronizerAddInput { synchronized: Boolean } +input SynchronizerAddAutoUserInput { + user_name: String! + confidence_level: Int! +} + input SynchronizerFetchInput { uri: String! token: String @@ -1361,7 +1369,7 @@ interface BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] } @@ -1576,6 +1584,7 @@ type Settings implements InternalObject & BasicObject & ThemeSettings & IntlSett password_policy_min_lowercase: Int password_policy_min_uppercase: Int platform_messages: [SettingsMessage!] + platform_session_max_concurrent: Int messages_administration: [SettingsMessage!] analytics_google_analytics_v4: String playground_enabled: Boolean! @@ -1635,7 +1644,7 @@ type Group implements InternalObject & BasicObject { standard_id: String! entity_type: String! auto_integration_assignation: [String]! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! default_assignation: Boolean @@ -1829,7 +1838,7 @@ type User implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] user_email: String! api_token: String! @@ -2014,7 +2023,7 @@ type Role implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -2048,7 +2057,7 @@ type Capability implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -2212,6 +2221,16 @@ type RabbitMQConnection { pass: String! } +type S3Connection { + endpoint: String! + port: Int! + use_ssl: Boolean! + bucket_name: String! + bucket_region: String! + access_key: String! + secret_key: String! +} + input ConnectorInfoInput { run_and_terminate: Boolean! buffering: Boolean! @@ -2232,6 +2251,7 @@ type ConnectorInfo { type ConnectorConfig { connection: RabbitMQConnection! + s3: S3Connection! listen: String! listen_routing: String! listen_exchange: String! @@ -2292,7 +2312,7 @@ type Connector implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! title: String! @@ -2339,7 +2359,7 @@ type ManagedConnector implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! connector_user_id: ID @@ -2367,7 +2387,7 @@ type ConnectorManager implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! public_key: String! @@ -2442,7 +2462,7 @@ interface StixObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2481,7 +2501,7 @@ interface StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] x_opencti_stix_ids: [StixId] is_inferred: Boolean! @@ -2520,7 +2540,7 @@ type MarkingDefinition implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2592,7 +2612,7 @@ type Label implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2652,7 +2672,7 @@ type ExternalReference implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2692,6 +2712,9 @@ input ExternalReferenceAddInput { url: String hash: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] external_id: String created: DateTime modified: DateTime @@ -2725,7 +2748,7 @@ type KillChainPhase implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2815,7 +2838,7 @@ interface StixCoreObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2916,7 +2939,7 @@ interface StixDomainObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3020,7 +3043,7 @@ type AttackPattern implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3113,6 +3136,10 @@ input AttackPatternAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type AttackPatternForMatrix { @@ -3172,7 +3199,7 @@ type Campaign implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3258,6 +3285,10 @@ input CampaignAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum ContainersOrdering { @@ -3289,7 +3320,7 @@ interface Container { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3366,7 +3397,7 @@ type Note implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3456,6 +3487,10 @@ input NoteAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } input NoteUserAddInput { @@ -3510,7 +3545,7 @@ type ObservedData implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3595,6 +3630,10 @@ input ObservedDataAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum OpinionsOrdering { @@ -3625,7 +3664,7 @@ type Opinion implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3711,6 +3750,9 @@ input OpinionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] } input OpinionUserAddInput { @@ -3763,7 +3805,7 @@ type Report implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3861,7 +3903,11 @@ input ReportAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } enum CoursesOfActionOrdering { @@ -3890,7 +3936,7 @@ type CourseOfAction implements BasicObject & StixObject & StixCoreObject & StixD id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3973,6 +4019,10 @@ input CourseOfActionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum IdentitiesOrdering { @@ -4006,7 +4056,7 @@ interface Identity { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4085,7 +4135,12 @@ input IdentityAddInput { clientMutationId: String created: DateTime modified: DateTime + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] update: Boolean + upsertOperations: [EditInput!] } enum IndividualsOrdering { @@ -4113,7 +4168,7 @@ type Individual implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4204,6 +4259,10 @@ input IndividualAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum SectorsOrdering { @@ -4232,7 +4291,7 @@ type Sector implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4319,6 +4378,10 @@ input SectorAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum SystemsOrdering { @@ -4346,7 +4409,7 @@ type System implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4436,6 +4499,10 @@ input SystemAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum InfrastructuresOrdering { @@ -4469,7 +4536,7 @@ type Infrastructure implements BasicObject & StixObject & StixCoreObject & StixD id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4555,6 +4622,10 @@ input InfrastructureAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum IntrusionSetsOrdering { @@ -4585,7 +4656,7 @@ type IntrusionSet implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4678,6 +4749,10 @@ input IntrusionSetAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum LocationsOrdering { @@ -4707,7 +4782,7 @@ interface Location { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4787,6 +4862,11 @@ input LocationAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum PositionsOrdering { @@ -4815,7 +4895,7 @@ type Position implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4903,6 +4983,10 @@ input PositionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum CitiesOrdering { @@ -4932,7 +5016,7 @@ type City implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5018,6 +5102,10 @@ input CityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum CountriesOrdering { @@ -5044,7 +5132,7 @@ type Country implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5128,6 +5216,10 @@ input CountryAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum RegionsOrdering { @@ -5154,7 +5246,7 @@ type Region implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5240,6 +5332,10 @@ input RegionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum MalwaresOrdering { @@ -5278,7 +5374,7 @@ type Malware implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5375,6 +5471,10 @@ input MalwareAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum ThreatActorsOrdering { @@ -5406,7 +5506,7 @@ interface ThreatActor implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5487,7 +5587,7 @@ type ThreatActorGroup implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5588,6 +5688,10 @@ input ThreatActorGroupAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum ToolsOrdering { @@ -5615,7 +5719,7 @@ type Tool implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5699,6 +5803,10 @@ input ToolAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum VulnerabilitiesOrdering { @@ -5730,7 +5838,7 @@ type Vulnerability implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5905,6 +6013,10 @@ input VulnerabilityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum IncidentsOrdering { @@ -5940,7 +6052,7 @@ type Incident implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6033,6 +6145,10 @@ input IncidentAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum StixCyberObservablesOrdering { @@ -6060,7 +6176,7 @@ interface StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6107,7 +6223,7 @@ type AutonomousSystem implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6159,13 +6275,17 @@ input AutonomousSystemAddInput { name: String rir: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Directory implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6221,13 +6341,17 @@ input DirectoryAddInput { mtime: DateTime atime: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6275,13 +6399,17 @@ type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyber input DomainNameAddInput { value: String! file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type EmailAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6331,13 +6459,17 @@ input EmailAddrAddInput { value: String display_name: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type EmailMessage implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6397,13 +6529,17 @@ input EmailMessageAddInput { received_lines: [String] body: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type EmailMimePartType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6455,6 +6591,10 @@ input EmailMimePartTypeAddInput { content_type: String content_disposition: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } input HashInput { @@ -6463,6 +6603,7 @@ input HashInput { "*Constraints:*\n* Minimal length: `5`\n" hash: String! + upsertOperations: [EditInput!] } type Hash { @@ -6484,7 +6625,7 @@ interface HashedObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6532,7 +6673,7 @@ type Artifact implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6592,13 +6733,17 @@ input ArtifactAddInput { decryption_key: String x_opencti_additional_names: [String] file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type StixFile implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6667,13 +6812,17 @@ input StixFileAddInput { x_opencti_additional_names: [String] obsContent: ID file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type X509Certificate implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6775,13 +6924,17 @@ input X509CertificateAddInput { certificate_policies: String policy_mappings: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type IPv4Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6832,13 +6985,17 @@ input IPv4AddrAddInput { belongsTo: [String] resolvesTo: [String] file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6887,13 +7044,17 @@ type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb input IPv6AddrAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6941,13 +7102,17 @@ type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObs input MacAddrAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6995,13 +7160,17 @@ type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObser input MutexAddInput { name: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type NetworkTraffic implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7072,13 +7241,17 @@ input NetworkTrafficAddInput { src_packets: Int dst_packets: Int file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Process implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7168,13 +7341,17 @@ input ProcessAddInput { service_type: String service_status: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Software implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7245,13 +7422,17 @@ input SoftwareAddInput { version: String x_opencti_product: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7299,13 +7480,17 @@ type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObserva input UrlAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type UserAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7380,13 +7565,17 @@ input UserAccountAddInput { account_first_login: DateTime account_last_login: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type WindowsRegistryKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7437,14 +7626,18 @@ input WindowsRegistryKeyAddInput { attribute_key: String modified_time: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] number_of_subkeys: Int + upsertOperations: [EditInput!] } type WindowsRegistryValueType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7496,13 +7689,17 @@ input WindowsRegistryValueTypeAddInput { data: String data_type: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type CryptographicKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7550,13 +7747,17 @@ type CryptographicKey implements BasicObject & StixObject & StixCoreObject & Sti input CryptographicKeyAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7604,13 +7805,17 @@ type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & input CryptocurrencyWalletAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7658,13 +7863,17 @@ type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberOb input HostnameAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7712,13 +7921,17 @@ type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObserv input TextAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7766,13 +7979,17 @@ type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberO input UserAgentAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type BankAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7824,13 +8041,17 @@ input BankAccountAddInput { bic: String account_number: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7878,13 +8099,17 @@ type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixC input TrackingNumberAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Credential implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7932,13 +8157,17 @@ type Credential implements BasicObject & StixObject & StixCoreObject & StixCyber input CredentialAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7986,13 +8215,17 @@ type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCybe input PhoneNumberAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type PaymentCard implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8046,13 +8279,17 @@ input PaymentCardAddInput { cvv: Int holder_name: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type MediaContent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8109,13 +8346,17 @@ input MediaContentAddInput { url: String! publication_date: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8165,13 +8406,18 @@ input PersonaAddInput { "*Constraints:*\n* Minimal length: `2`\n* Must match format: `not-blank`\n" persona_name: String! persona_type: String! + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type SSHKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8236,13 +8482,18 @@ input SSHKeyAddInput { comment: String created: DateTime expiration_date: DateTime + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } interface BasicRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8256,7 +8507,7 @@ type InternalRelationship implements BasicRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8356,7 +8607,7 @@ interface StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8434,7 +8685,7 @@ type StixCoreRelationship implements BasicRelationship & StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8515,6 +8766,7 @@ input StixCoreRelationshipAddInput { coverage_information: [SecurityCoverageExpectation!] clientMutationId: String update: Boolean + upsertOperations: [EditInput!] } enum StixSightingRelationshipsOrdering { @@ -8552,7 +8804,7 @@ type StixSightingRelationship implements BasicRelationship & StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8622,6 +8874,7 @@ input StixSightingRelationshipAddInput { update: Boolean x_opencti_workflow_id: String x_opencti_modified_at: DateTime + upsertOperations: [EditInput!] } enum StixRefRelationshipsOrdering { @@ -8657,7 +8910,7 @@ type StixRefRelationship implements BasicRelationship & StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8716,6 +8969,9 @@ input StixRefRelationshipAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] } input StixRefRelationshipsAddInput { @@ -8724,6 +8980,17 @@ input StixRefRelationshipsAddInput { toIds: [StixRef!]! } +type SynchronizerAddInputFromImport { + name: String! + uri: String! + stream_id: String! + current_state_date: DateTime + listen_deletion: Boolean! + ssl_verify: Boolean! + no_dependencies: Boolean! + synchronized: Boolean! +} + type Query { stix(id: String!): String enrichmentConnectors(type: String!): [Connector] @@ -8802,6 +9069,7 @@ type Query { rules: [Rule] ruleManagerInfo: RuleManager synchronizer(id: String!): Synchronizer + synchronizerAddInputFromImport(file: Upload!): SynchronizerAddInputFromImport! synchronizers(first: Int, after: ID, orderBy: SynchronizersOrdering, orderMode: OrderingMode, search: String): SynchronizerConnection synchronizerFetch(input: SynchronizerFetchInput): [RemoteStreamCollection] stixMetaObject(id: String!): StixMetaObject @@ -9032,6 +9300,7 @@ type Query { ingestionRsss(first: Int, after: ID, orderBy: IngestionRssOrdering, orderMode: OrderingMode, filters: FilterGroup, includeAuthorities: Boolean, search: String): IngestionRssConnection ingestionTaxii(id: String!): IngestionTaxii ingestionTaxiis(first: Int, after: ID, orderBy: IngestionTaxiiOrdering, orderMode: OrderingMode, filters: FilterGroup, includeAuthorities: Boolean, search: String): IngestionTaxiiConnection + taxiiFeedAddInputFromImport(file: Upload!): TaxiiFeedAddInputFromImport! ingestionTaxiiCollection(id: String!): IngestionTaxiiCollection ingestionTaxiiCollections(first: Int, after: ID, orderBy: IngestionTaxiiCollectionOrdering, orderMode: OrderingMode, filters: FilterGroup, includeAuthorities: Boolean, search: String): IngestionTaxiiCollectionConnection ingestionCsv(id: String!): IngestionCsv @@ -9607,6 +9876,7 @@ type Mutation { createDraftAndAskJobImport(fileName: ID!, connectorId: String, configuration: String, bypassEntityId: String, bypassValidation: Boolean, validationMode: ValidationMode, forceValidation: Boolean, authorized_members: [MemberAccessInput!]): File resetFileIndexing: Boolean synchronizerAdd(input: SynchronizerAddInput!): Synchronizer + synchronizerAddAutoUser(id: ID!, input: SynchronizerAddAutoUserInput!): Synchronizer synchronizerEdit(id: ID!): SynchronizerEditMutations synchronizerStart(id: ID!): Synchronizer synchronizerStop(id: ID!): Synchronizer @@ -9740,11 +10010,11 @@ type Mutation { vulnerabilityEdit(id: ID!): VulnerabilityEditMutations incidentAdd(input: IncidentAddInput!): Incident incidentEdit(id: ID!): IncidentEditMutations - stixCyberObservableAdd(type: String!, stix_id: StixId, x_opencti_score: Int, x_opencti_description: String, x_opencti_modified_at: DateTime, createIndicator: Boolean, createdBy: String, objectMarking: [String], objectLabel: [String], objectOrganization: [String], externalReferences: [String], clientMutationId: String, update: Boolean, AutonomousSystem: AutonomousSystemAddInput, Directory: DirectoryAddInput, DomainName: DomainNameAddInput, EmailAddr: EmailAddrAddInput, EmailMessage: EmailMessageAddInput, EmailMimePartType: EmailMimePartTypeAddInput, Artifact: ArtifactAddInput, StixFile: StixFileAddInput, X509Certificate: X509CertificateAddInput, IPv4Addr: IPv4AddrAddInput, IPv6Addr: IPv6AddrAddInput, MacAddr: MacAddrAddInput, Mutex: MutexAddInput, NetworkTraffic: NetworkTrafficAddInput, Process: ProcessAddInput, Software: SoftwareAddInput, Url: UrlAddInput, UserAccount: UserAccountAddInput, WindowsRegistryKey: WindowsRegistryKeyAddInput, WindowsRegistryValueType: WindowsRegistryValueTypeAddInput, CryptographicKey: CryptographicKeyAddInput, CryptocurrencyWallet: CryptocurrencyWalletAddInput, Hostname: HostnameAddInput, Text: TextAddInput, UserAgent: UserAgentAddInput, BankAccount: BankAccountAddInput, Credential: CredentialAddInput, TrackingNumber: TrackingNumberAddInput, PhoneNumber: PhoneNumberAddInput, PaymentCard: PaymentCardAddInput, MediaContent: MediaContentAddInput, Persona: PersonaAddInput, SSHKey: SSHKeyAddInput): StixCyberObservable + stixCyberObservableAdd(type: String!, stix_id: StixId, x_opencti_score: Int, x_opencti_description: String, x_opencti_modified_at: DateTime, createIndicator: Boolean, createdBy: String, objectMarking: [String], objectLabel: [String], objectOrganization: [String], externalReferences: [String], clientMutationId: String, update: Boolean, upsertOperations: [EditInput!], AutonomousSystem: AutonomousSystemAddInput, Directory: DirectoryAddInput, DomainName: DomainNameAddInput, EmailAddr: EmailAddrAddInput, EmailMessage: EmailMessageAddInput, EmailMimePartType: EmailMimePartTypeAddInput, Artifact: ArtifactAddInput, StixFile: StixFileAddInput, X509Certificate: X509CertificateAddInput, IPv4Addr: IPv4AddrAddInput, IPv6Addr: IPv6AddrAddInput, MacAddr: MacAddrAddInput, Mutex: MutexAddInput, NetworkTraffic: NetworkTrafficAddInput, Process: ProcessAddInput, Software: SoftwareAddInput, Url: UrlAddInput, UserAccount: UserAccountAddInput, WindowsRegistryKey: WindowsRegistryKeyAddInput, WindowsRegistryValueType: WindowsRegistryValueTypeAddInput, CryptographicKey: CryptographicKeyAddInput, CryptocurrencyWallet: CryptocurrencyWalletAddInput, Hostname: HostnameAddInput, Text: TextAddInput, UserAgent: UserAgentAddInput, BankAccount: BankAccountAddInput, Credential: CredentialAddInput, TrackingNumber: TrackingNumberAddInput, PhoneNumber: PhoneNumberAddInput, PaymentCard: PaymentCardAddInput, MediaContent: MediaContentAddInput, Persona: PersonaAddInput, SSHKey: SSHKeyAddInput): StixCyberObservable stixCyberObservableEdit(id: ID!): StixCyberObservableEditMutations stixCyberObservablesExportAsk(input: StixCyberObservablesExportAskInput!): [File!] stixCyberObservablesExportPush(entity_id: String, entity_type: String!, file: Upload!, file_markings: [String]!, listFilters: String): Boolean - artifactImport(file: Upload!, x_opencti_description: String, createdBy: String, objectMarking: [String], objectLabel: [String]): Artifact + artifactImport(file: Upload!, fileMarkings: [String], x_opencti_description: String, createdBy: String, objectMarking: [String], objectLabel: [String]): Artifact stixRelationshipEdit(id: ID!): StixRelationshipEditMutations stixCoreRelationshipAdd(input: StixCoreRelationshipAddInput, reversedReturn: Boolean): StixCoreRelationship stixCoreRelationshipEdit(id: ID!): StixCoreRelationshipEditMutations @@ -9901,6 +10171,7 @@ type Mutation { ingestionTaxiiDelete(id: ID!): ID ingestionTaxiiResetState(id: ID!): IngestionTaxii ingestionTaxiiFieldPatch(id: ID!, input: [EditInput!]!): IngestionTaxii + ingestionTaxiiAddAutoUser(id: ID!, input: IngestionTaxiiAddAutoUserInput!): IngestionTaxii ingestionTaxiiCollectionAdd(input: IngestionTaxiiCollectionAddInput!): IngestionTaxiiCollection ingestionTaxiiCollectionDelete(id: ID!): ID ingestionTaxiiCollectionFieldPatch(id: ID!, input: [EditInput!]!): IngestionTaxiiCollection @@ -10035,13 +10306,14 @@ type Mutation { autoRegisterOpenCTI(input: AutoRegisterInput!): Success! contactUsXtmHub: Success! metricPatch(id: ID!, input: PatchMetricInput!): BasicObject + runMigration(migrationName: String!): Boolean! } type Channel implements BasicObject & StixObject & StixCoreObject & StixDomainObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10145,6 +10417,10 @@ input ChannelAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Catalog implements InternalObject & BasicObject { @@ -10182,7 +10458,7 @@ type Language implements BasicObject & StixCoreObject & StixDomainObject & StixO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10280,13 +10556,17 @@ input LanguageAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] } type Event implements BasicObject & StixCoreObject & StixDomainObject & StixObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10394,13 +10674,17 @@ input EventAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Grouping implements BasicObject & StixObject & StixCoreObject & StixDomainObject & Container { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10519,14 +10803,18 @@ input GroupingAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } type Narrative implements BasicObject & StixCoreObject & StixDomainObject & StixObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10630,8 +10918,12 @@ input NarrativeAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] x_opencti_modified_at: DateTime x_opencti_workflow_id: String + upsertOperations: [EditInput!] } type ResolvedInstanceFilter { @@ -10693,7 +10985,7 @@ type Trigger implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -10783,7 +11075,7 @@ type Notification implements InternalObject & BasicObject { created: DateTime name: String! notification_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] notification_content: [NotificationContent!]! is_read: Boolean! @@ -10807,7 +11099,7 @@ type DataComponent implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10907,15 +11199,19 @@ input DataComponentAddInput { aliases: [String] dataSource: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] x_opencti_workflow_id: String x_opencti_modified_at: DateTime + upsertOperations: [EditInput!] } type DataSource implements BasicObject & StixObject & StixCoreObject & StixDomainObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11018,8 +11314,12 @@ input DataSourceAddInput { collection_layers: [String!] dataComponents: [String] file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] x_opencti_workflow_id: String x_opencti_modified_at: DateTime + upsertOperations: [EditInput!] } enum VocabularyCategory { @@ -11090,7 +11390,7 @@ type Vocabulary implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! creators: [Creator!] @@ -11152,7 +11452,7 @@ type AdministrativeArea implements BasicObject & StixCoreObject & StixDomainObje id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11258,13 +11558,17 @@ input AdministrativeAreaAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Task implements Container & StixDomainObject & StixCoreObject & StixObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11370,13 +11674,18 @@ input TaskAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type TaskTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -11415,7 +11724,7 @@ interface Case implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11508,7 +11817,7 @@ type CaseTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -11546,7 +11855,7 @@ type CaseIncident implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11672,9 +11981,13 @@ input CaseIncidentAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String update: Boolean authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } type RfiRequestAccessConfiguration { @@ -11686,7 +11999,7 @@ type CaseRfi implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11810,19 +12123,23 @@ input CaseRfiAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String update: Boolean information_types: [String!] caseTemplates: [String!] authorized_members: [MemberAccessInput!] x_opencti_request_access: String + upsertOperations: [EditInput!] } type CaseRft implements BasicObject & StixObject & StixCoreObject & StixDomainObject & Container & Case { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11942,6 +12259,9 @@ input CaseRftAddInput { created: DateTime modified: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String x_opencti_modified_at: DateTime x_opencti_workflow_id: String @@ -11949,13 +12269,14 @@ input CaseRftAddInput { takedown_types: [String!] caseTemplates: [String!] authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } type Feedback implements BasicObject & StixObject & StixCoreObject & StixDomainObject & Container & Case { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -12070,9 +12391,13 @@ input FeedbackAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String update: Boolean rating: Int + upsertOperations: [EditInput!] } type DefaultValue { @@ -12230,7 +12555,7 @@ type MalwareAnalysis implements BasicObject & StixCoreObject & StixDomainObject id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -12357,6 +12682,10 @@ input MalwareAnalysisAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type ManagerConfiguration implements InternalObject & BasicObject { @@ -12397,7 +12726,7 @@ type Notifier implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -12449,7 +12778,7 @@ type ThreatActorIndividual implements BasicObject & StixObject & StixCoreObject id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -12596,6 +12925,10 @@ input ThreatActorIndividualAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type PlayBookExecutionStep { @@ -12785,6 +13118,7 @@ type IngestionTaxii implements InternalObject & BasicObject { ingestion_running: Boolean last_execution_date: DateTime confidence_to_score: Boolean + toConfigurationExport: String! } enum IngestionTaxiiOrdering { @@ -12806,6 +13140,17 @@ type IngestionTaxiiEdge { node: IngestionTaxii! } +type TaxiiFeedAddInputFromImport { + name: String! + description: String! + uri: String! + version: String! + collection: String! + authentication_type: String! + authentication_value: String! + added_after_start: String +} + input IngestionTaxiiAddInput { "*Constraints:*\n* Minimal length: `2`\n* Must match format: `not-blank`\n" name: String! @@ -12822,7 +13167,14 @@ input IngestionTaxiiAddInput { added_after_start: DateTime ingestion_running: Boolean confidence_to_score: Boolean - user_id: String + user_id: String! + automatic_user: Boolean + confidence_level: Int +} + +input IngestionTaxiiAddAutoUserInput { + user_name: String! + confidence_level: Int! } type IngestionTaxiiCollection implements InternalObject & BasicObject { @@ -12873,7 +13225,7 @@ type IngestionCsv implements InternalObject & BasicObject { id: ID! entity_type: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime updated_at: DateTime @@ -12979,7 +13331,7 @@ type IngestionJson implements InternalObject & BasicObject { entity_type: String! connector_id: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime updated_at: DateTime @@ -13135,7 +13487,7 @@ type Indicator implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -13245,14 +13597,18 @@ input IndicatorAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] basedOn: [String!] + upsertOperations: [EditInput!] } type DecayRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! updated_at: DateTime! @@ -13308,7 +13664,7 @@ type DecayExclusionRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! name: String! @@ -13345,7 +13701,7 @@ type Organization implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -13470,6 +13826,10 @@ input OrganizationAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type MeOrganization { @@ -13972,7 +14332,7 @@ type SupportPackage implements InternalObject & BasicObject { name: String! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! package_status: PackageStatus! @@ -14021,7 +14381,7 @@ type ExclusionList implements InternalObject & BasicObject { description: String standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! enabled: Boolean! @@ -14128,7 +14488,7 @@ type FintelTemplate implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! settings_types: [String!]! @@ -14176,7 +14536,7 @@ type DisseminationList implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! updated_at: DateTime! @@ -14221,7 +14581,7 @@ type SavedFilter implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! filters: String! @@ -14349,7 +14709,7 @@ type PirRelationship implements BasicRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -14456,7 +14816,7 @@ type FintelDesign implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -14496,7 +14856,7 @@ type SecurityPlatform implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -14595,6 +14955,11 @@ input SecurityPlatformAddInput { x_opencti_modified_at: DateTime externalReferences: [String] update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type CoverageResult { @@ -14606,7 +14971,7 @@ type SecurityCoverage implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -14719,6 +15084,10 @@ input SecurityCoverageAddInput { x_opencti_modified_at: DateTime external_uri: String externalReferences: [String] + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] update: Boolean auto_enrichment_disable: Boolean! periodicity: String @@ -14759,7 +15128,7 @@ type EmailTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -14821,6 +15190,7 @@ type FormConnection { } enum FormsOrdering { + _score name created_at updated_at diff --git a/opencti-platform/opencti-front/src/utils/field.ts b/opencti-platform/opencti-front/src/utils/field.ts deleted file mode 100644 index d457facd0acb..000000000000 --- a/opencti-platform/opencti-front/src/utils/field.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const fieldSpacingContainerStyle = { marginTop: 20, width: '100%' }; - -export interface FieldOption { - id?: string; - value: string; - label: string; - color?: string; - type?: string; - standard_id?: string; -} - -// TODO move this interface inside file KillChainPhasesField -// when it has been transformed it TypeScript. -export interface KillChainPhaseFieldOption extends FieldOption { - kill_chain_name: string; - phase_name: string; -} diff --git a/opencti-platform/opencti-front/src/utils/field.tsx b/opencti-platform/opencti-front/src/utils/field.tsx new file mode 100644 index 000000000000..4bafdcd6886d --- /dev/null +++ b/opencti-platform/opencti-front/src/utils/field.tsx @@ -0,0 +1,25 @@ +import { ComponentType } from 'react'; +import { FieldConfig, Field as FormikField } from 'formik'; + +export const fieldSpacingContainerStyle = { marginTop: 20, width: '100%' }; + +export interface FieldOption { + id?: string; + value: string; + label: string; + color?: string; + type?: string; + standard_id?: string; +} + +type FormikFieldConfig

= Omit, 'component' | 'as' | 'render' | 'children'>; +type NoMetaProps

= Omit; +type FieldProps = FormikFieldConfig & NoMetaProps & { + component: ComponentType; +}; + +const Field = (props: FieldProps) => { + return ; +}; + +export default Field; diff --git a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx index c80e9bef48a9..b3a4c1218ecc 100644 --- a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx @@ -3,6 +3,7 @@ import { buildFiltersAndOptionsForWidgets, emptyFilterGroup, findFiltersFromKeys, + formatFiltersInPirContext, getEntityTypeTwoFirstLevelsFilterValues, isRegardingOfFilterWarning, removeIdAndIncorrectKeysFromFilterGroupObject, @@ -703,3 +704,52 @@ describe('buildFiltersAndOptionsForWidgets', () => { expect(filters).toStrictEqual(expectedFilters); }); }); + +describe('formatFiltersInPirContext', () => { + it('should format filters in a PIR context', () => { + const initialFilters = { + mode: 'and', + filters: [ + { key: 'entity_type', values: ['Malware'] }, + { key: 'pir_score', values: ['50'], operator: 'gt' }, + ], + filterGroups: [ + { + mode: 'or', + filters: [ + { key: 'objectLabel', values: ['label-1'] }, + { key: 'last_pir_score_date', values: ['now-7d', 'now'], operator: 'within' }, + ], + filterGroups: [], + }, + ], + }; + const expectedFormattedFilters = { + mode: 'and', + filters: [ + { key: 'entity_type', values: ['Malware'] }, + { key: 'pir_score', + values: [ + { key: 'score', values: ['50'], operator: 'gt' }, + { key: 'pir_ids', values: ['pir-id-1'] }, + ] }, + ], + filterGroups: [ + { + mode: 'or', + filters: [ + { key: 'objectLabel', values: ['label-1'] }, + { key: 'last_pir_score_date', + values: [ + { key: 'date', values: ['now-7d', 'now'], operator: 'within' }, + { key: 'pir_ids', values: ['pir-id-1'] }, + ] }, + ], + filterGroups: [], + }, + ], + }; + const result = formatFiltersInPirContext(initialFilters, 'pir-id-1'); + expect(result).toEqual(expectedFormattedFilters); + }); +}); diff --git a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx index 2dcd7cc9f026..e3e5c0042824 100644 --- a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx @@ -45,32 +45,45 @@ export const ME_FILTER_VALUE = '@me'; // 'within' operator filter constants export const DEFAULT_WITHIN_FILTER_VALUES = ['now-1d', 'now']; +const PIR_SCORE_FILTER = 'pir_score'; +const LAST_PIR_SCORE_DATE_FILTER = 'last_pir_score_date'; + export const FiltersVariant = { list: 'list', dialog: 'dialog', }; -const NOT_CLEANABLE_FILTER_KEYS = ['entity_type', 'authorized_members.id', 'user_id', 'internal_id', 'entity_id', 'ids', 'bulkSearchKeywords']; +const NOT_CLEANABLE_FILTER_KEYS = [ + 'entity_type', + 'authorized_members.id', + 'user_id', + 'internal_id', + 'entity_id', + 'ids', + 'bulkSearchKeywords', + PIR_SCORE_FILTER, + LAST_PIR_SCORE_DATE_FILTER, +]; -const pirScoreFilterDefinition = (pirId: string) => ({ - filterKey: `pir_score.${pirId}`, +const pirScoreFilterDefinition = { + filterKey: PIR_SCORE_FILTER, label: 'PIR Score', multiple: false, type: 'integer', subFilters: [], subEntityTypes: [], elementsForFilterValuesSearch: [], -}); +}; -const lastPirScoreDateFilterDefinition = (pirId: string) => ({ - filterKey: `last_pir_score_date.${pirId}`, +const lastPirScoreDateFilterDefinition = { + filterKey: LAST_PIR_SCORE_DATE_FILTER, label: 'Last PIR Score date', multiple: false, type: 'date', subFilters: [], subEntityTypes: [], elementsForFilterValuesSearch: [], -}); +}; // filters which possible values are entity types or relationship types export const entityTypesFilters = [ @@ -815,7 +828,7 @@ export const useAvailableFilterKeysForEntityTypes = (entityTypes: string[]) => { const isFilterKeyAvailable = (key: string, availableFilterKeys: string[]) => { const completedAvailableFilterKeys = availableFilterKeys.concat(NOT_CLEANABLE_FILTER_KEYS); - return completedAvailableFilterKeys.includes(key) || key.startsWith('pir_score') || key.startsWith('last_pir_score_date'); + return completedAvailableFilterKeys.includes(key); }; export const removeIdFromFilterGroupObject = (filters?: FilterGroup | null): FilterGroup | undefined => { @@ -906,13 +919,11 @@ export const getFilterDefinitionFromFilterKeysMap = ( filterKeysMap: Map, ): FilterDefinition | undefined => { const filterKey = getStringFilterKey(key); - if (filterKey.startsWith('pir_score.')) { - const pirId = filterKey.split('.')[1]; - return pirScoreFilterDefinition(pirId); + if (filterKey === PIR_SCORE_FILTER) { + return pirScoreFilterDefinition; } - if (filterKey.startsWith('last_pir_score_date.')) { - const pirId = filterKey.split('.')[1]; - return lastPirScoreDateFilterDefinition(pirId); + if (filterKey === LAST_PIR_SCORE_DATE_FILTER) { + return lastPirScoreDateFilterDefinition; } return filterKeysMap.get(filterKey); }; @@ -923,13 +934,11 @@ export const useFilterDefinition = ( subKey?: string, ): FilterDefinition | undefined => { const filterKey = getStringFilterKey(key); - if (filterKey.startsWith('pir_score.')) { - const pirId = filterKey.split('.')[1]; - return pirScoreFilterDefinition(pirId); + if (filterKey === PIR_SCORE_FILTER) { + return pirScoreFilterDefinition; } - if (filterKey.startsWith('last_pir_score_date.')) { - const pirId = filterKey.split('.')[1]; - return lastPirScoreDateFilterDefinition(pirId); + if (filterKey === LAST_PIR_SCORE_DATE_FILTER) { + return lastPirScoreDateFilterDefinition; } const filterDefinition = useBuildFilterKeysMapFromEntityType(entityTypes).get(filterKey); if (subKey) { @@ -1113,3 +1122,30 @@ export const getFilterKeyValues = (filterKey: string, filterGroup: FilterGroup) }); return values; }; + +// add pirId to pir filters +export const formatFiltersInPirContext = (f: FilterGroup, pirId: string): FilterGroup => { + const formattedFilters = []; + for (const filter of f.filters) { + const filterKey = filter.key; + if (filterKey === PIR_SCORE_FILTER || filterKey === LAST_PIR_SCORE_DATE_FILTER) { + const subKey = filterKey === PIR_SCORE_FILTER ? 'score' : 'date'; + formattedFilters.push({ + key: filterKey, + values: [ + { ...filter, key: subKey }, + { key: 'pir_ids', values: [pirId] }, + ], + }); + } else { + formattedFilters.push(filter); + } + } + return { + mode: f.mode, + filters: formattedFilters, + filterGroups: f.filterGroups.length > 0 + ? f.filterGroups.map((fg) => formatFiltersInPirContext(fg, pirId)) + : [], + }; +}; diff --git a/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx b/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx index 7d71ce2b2cc0..bf5078d67122 100644 --- a/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx @@ -325,6 +325,11 @@ const useSearchEntities = ({ value: 'object', type: 'stix-internal-relationship', }; + const labelRelationshipType = { + label: t_i18n('relationship_object-label'), + value: 'objectLabel', + type: 'stix-meta-relationship', + }; const unionSetEntities = (key: string, newEntities: EntityValue[]) => setEntities((c) => ({ ...c, @@ -918,6 +923,7 @@ const useSearchEntities = ({ ...scrTypes, abstractTypeFilterValue('stix-sighting-relationship'), objectRelationshipType, + labelRelationshipType, ]; } else { // display relationship types according to searchContext.entityTypes const { entityTypes } = searchContext; @@ -936,6 +942,12 @@ const useSearchEntities = ({ objectRelationshipType, ]; } + if (entityTypes.includes('object-label')) { + relationshipsTypes = [ + ...relationshipsTypes, + labelRelationshipType, + ]; + } } unionSetEntities(filterKey, relationshipsTypes.sort((a, b) => a.label.localeCompare(b.label))); break; diff --git a/opencti-platform/opencti-front/src/utils/htmlToPdf/htmlToPdf.ts b/opencti-platform/opencti-front/src/utils/htmlToPdf/htmlToPdf.ts index fdb2d6a5b34c..76dbce974fad 100644 --- a/opencti-platform/opencti-front/src/utils/htmlToPdf/htmlToPdf.ts +++ b/opencti-platform/opencti-front/src/utils/htmlToPdf/htmlToPdf.ts @@ -111,6 +111,7 @@ export const htmlToPdfReport = async ( imagesByReference: true, ignoreStyles: ['font-family'], // Ignoring fonts to force Roboto later. defaultStyles: { + h1: { margin: [0, 20, 0, 10], color: DARK, fontSize: 32 }, h2: { margin: [0, 20, 0, 10], color: DARK, fontSize: 28 }, h3: { margin: [0, 20, 0, 10], color: DARK, fontSize: 24 }, th: { bold: true, fillColor: '', font: selectedFont }, diff --git a/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts new file mode 100644 index 000000000000..fe6d990931ad --- /dev/null +++ b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts @@ -0,0 +1,145 @@ +import { describe, it, expect } from 'vitest'; +import removeUnnecessaryHtml from './pdfUnnecessarytHtml'; + +describe('Utils: removeUnnecessaryHtml', () => { + it('should remove script tags and their content', () => { + const html = '

ContentMore content
'; + const result = removeUnnecessaryHtml(html); + expect(result).not.toContain(' +

Regular paragraph content.

+ + +

More important content that must be preserved.

+ +
+
+ `; + const result = removeUnnecessaryHtml(html); + + // Verify structure is preserved + expect(result).toContain('article-container'); + expect(result).toContain('Article Title'); + expect(result).toContain('article-body'); + + // Verify all paragraphs are preserved + expect(result).toContain('Lead paragraph with critical info.'); + expect(result).toContain('Regular paragraph content.'); + expect(result).toContain('More important content that must be preserved.'); + + // Verify unwanted elements are removed + expect(result).not.toContain(''); + expect(result).not.toContain(''); + expect(result).not.toContain(''); + expect(result).not.toContain('Please enable JavaScript'); + }); +}); diff --git a/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts index 9b11dbc0a1c1..69435bae1f47 100644 --- a/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts +++ b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts @@ -1,13 +1,40 @@ /** * There are some tags we don't want in the generated PDF. + * This function cleans up HTML content to ensure proper PDF generation. * * @param content The content to analyse. - * @returns Content without necessary stuff. + * @returns Content without unnecessary stuff but preserving article body. */ const removeUnnecessaryHtml = (content: string) => { - return content - .replaceAll('id="undefined" ', '') // ??? - .replaceAll(/]+src=(\\?["'])[^'"]+\.gif\1[^>]*\/?>/gi, ''); // Remove GIFs from content. + let cleanedContent = content + .replace(/id="undefined" /g, '') // Remove undefined IDs + .replace(/]+src=(\\?["'])[^'"]+\.gif\1[^>]*\/?>/gi, ''); // Remove GIFs from content + + // Remove script tags and their content + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/script>/gi, ''); + + // Remove style tags and their content (inline styles on elements are preserved) + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/style>/gi, ''); + + // Remove iframe tags + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/iframe>/gi, ''); + + // Remove noscript tags + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/noscript>/gi, ''); + + // Remove comments + cleanedContent = cleanedContent.replace(//g, ''); + + // Remove empty class attributes + cleanedContent = cleanedContent.replace(/\s+class=""\s*/g, ' '); + + // Remove empty style attributes + cleanedContent = cleanedContent.replace(/\s+style=""\s*/g, ' '); + + // Clean up multiple consecutive whitespace (but preserve single spaces and newlines) + cleanedContent = cleanedContent.replace(/[ \t]+/g, ' '); + + return cleanedContent; }; export default removeUnnecessaryHtml; diff --git a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx index 25f44e71e8b8..e5df99fdf589 100644 --- a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx +++ b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx @@ -40,7 +40,7 @@ describe('widgetUtils', () => { expect(getCurrentCategory('heatmap')).toBe('timeseries'); expect(getCurrentCategory('tree')).toBe('distribution'); expect(getCurrentCategory('map')).toBe('distribution'); - expect(getCurrentCategory('bookmark')).toBe('timeseries'); + expect(getCurrentCategory('bookmark')).toBe('list'); expect(getCurrentCategory('wordcloud')).toBe('distribution'); }); diff --git a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx index 44640197c020..ad5bdbcd4a96 100644 --- a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx +++ b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx @@ -185,7 +185,7 @@ const widgetVisualizationTypes = [ key: 'bookmark', name: 'Bookmark', dataSelectionLimit: 1, - category: 'timeseries', + category: 'list', availableParameters: [], isRelationships: false, isEntities: true, diff --git a/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts b/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts index 1e563085809d..4f4d5f1c2394 100644 --- a/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts @@ -2,7 +2,7 @@ import ArtifactPage from '../model/Artifact.pageModel'; import ArtifactImportPage from '../model/ArtifactImport.pageModel'; import { expect, test } from '../fixtures/baseFixtures'; -test('Artifact error message in the absence of a file.', async ({ page }) => { +test('Artifact error message in the absence of a file.', { tag: ['@ce'] }, async ({ page }) => { const artifactPage = new ArtifactPage(page); const artifactImport = new ArtifactImportPage(page); await page.goto('/dashboard/observations/artifacts'); diff --git a/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts b/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts index 6a891fd0fb39..a85dc5ebd6d2 100644 --- a/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts @@ -9,7 +9,7 @@ import { runBackgroundTaskOnIncidentByFilter, runBackgroundTaskOnIncidentBySearc * @param page */ -test('Verify background tasks pre-requisites on incident search', async ({ page }) => { +test('Verify background tasks pre-requisites on incident search', { tag: ['@ce'] }, async ({ page }) => { const incidentPage = new EventsIncidentPage(page); await incidentPage.goto(); @@ -19,6 +19,6 @@ test('Verify background tasks pre-requisites on incident search', async ({ page await runBackgroundTaskOnIncidentBySearch(page, true); }); -test('Verify background tasks pre-requisites on data entity search', async ({ page }) => { +test('Verify background tasks pre-requisites on data entity search', { tag: ['@ce'] }, async ({ page }) => { await searchOnDataEntitiesPerLabels(page, true); }); diff --git a/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts b/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts index bf89e93fc31e..7185ac5df533 100644 --- a/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts @@ -20,7 +20,7 @@ import { runBackgroundTaskOnIncidentByFilter, runBackgroundTaskOnIncidentBySearc * @param page */ -const waitAndRefreshUntilFirstTaskInStatus = async (page:Page, tasksPage: DataProcessingTasksPage, status: string, expectVisible: boolean) => { +const waitAndRefreshUntilFirstTaskInStatus = async (page: Page, tasksPage: DataProcessingTasksPage, status: string, expectVisible: boolean) => { await tasksPage.goto(); await expect(tasksPage.getPage()).toBeVisible(); @@ -42,13 +42,12 @@ const waitAndRefreshUntilFirstTaskInStatus = async (page:Page, tasksPage: DataPr let isStatusOk = await isOneStatusTaskOk(); while (!isStatusOk && loopCurrent < loopCount) { - // eslint-disable-next-line no-await-in-loop isStatusOk = await isOneStatusTaskOk(); loopCurrent += 1; } }; -test('Verify background tasks execution', { tag: ['@mutation', '@incident', '@task', '@filter'] }, async ({ page }) => { +test('Verify background tasks execution', { tag: ['@ce', '@mutation'] }, async ({ page }) => { const incidentPage = new EventsIncidentPage(page); const tasksPage = new DataProcessingTasksPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts index e170073e70b1..b566257b17a5 100644 --- a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts @@ -25,7 +25,7 @@ test.describe.configure({ mode: 'serial' }); * Export/Import a dashboard. * Create Widget - see values - Delete Widget */ -test('Dashboard CRUD', async ({ page }) => { +test('Dashboard CRUD', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); const dashboardPage = new DashboardPage(page); const dashboardForm = new DashboardFormPage(page, 'Create dashboard'); diff --git a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts index a92b217afbb7..ec0da69573f8 100644 --- a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts @@ -17,7 +17,7 @@ import DashboardWidgetsPageModel from '../model/DashboardWidgets.pageModel'; import DashboardDetailsPage from '../model/dashboardDetails.pageModel'; import AccessRestrictionPageModel from '../model/AccessRestriction.pageModel'; -test('Dashboard restriction access', async ({ page }) => { +test('Dashboard restriction access', { tag: ['@ce'] }, async ({ page }) => { const leftBar = new LeftBarPage(page); const topBar = new TopMenuProfilePage(page); const dashboardPage = new DashboardPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts b/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts index bae10f462093..5f0752b9cd11 100644 --- a/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts @@ -2,7 +2,7 @@ import DraftsPage from 'tests_e2e/model/drafts.pageModel'; import RestrictionsPage from 'tests_e2e/model/restrictions.pageModel'; import { expect, test } from '../fixtures/baseFixtures'; -test.describe('Restricted Drafts', () => { +test.describe('Restricted Drafts', { tag: ['@ce'] }, () => { test('should allow to remove restrictions on a draft', async ({ page }) => { const draftName = `Restricted Draft E2E - ${Date.now()}`; diff --git a/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts b/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts index f4ff55509de6..da201099f79c 100644 --- a/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts @@ -28,7 +28,7 @@ const noBypassUserName = `NoBypassReferencesUser ${nowTime}`; const noBypassRoleName = `NoBypassReferencesRole ${nowTime}`; const noBypassGroupName = `NoBypassReferencesTestGroup ${nowTime}`; -test.describe('Create user with no references bypass capabilities', () => { +test.describe('Create user with no references bypass capabilities', { tag: ['@ce'] }, () => { test('Create basic user role', async ({ page }) => { const rolesSettingsPage = new RolesSettingsPage(page); const rolePage = new RolePage(page); @@ -93,7 +93,7 @@ test.describe('Create user with no references bypass capabilities', () => { }); }); -test.describe('Authenticate no bypass user', () => { +test.describe('Authenticate no bypass user', { tag: ['@ce'] }, () => { test.use({ storageState: { cookies: [], origins: [] } }); test('Authenticate basic user', async ({ page }) => { const dashboardPage = new DashboardPage(page); @@ -107,7 +107,7 @@ test.describe('Authenticate no bypass user', () => { }); }); -test('Add and remove observable from Observables tab of a Report as Admin user', async ({ page }) => { +test('Add and remove observable from Observables tab of a Report as Admin user', { tag: ['@ce'] }, async ({ page }) => { const reportPage = new ReportPage(page); const reportDetailsPage = new ReportDetailsPage(page); const reportForm = new ReportFormPage(page); @@ -158,7 +158,7 @@ test('Add and remove observable from Observables tab of a Report as Admin user', await page.locator('span').filter({ hasText: 'Enforce references' }).click(); }); -test.describe('Add and remove observable from Observables tab of a Report as noBypass user', () => { +test.describe('Add and remove observable from Observables tab of a Report as noBypass user', { tag: ['@ce'] }, () => { test.use({ storageState: noBypassUserAuthFile }); test('Run test as noBypass user', async ({ page }) => { const reportPage = new ReportPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts b/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts index 98f16ac11866..967caf9e1ab6 100644 --- a/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts @@ -4,7 +4,7 @@ import EventsIncidentPage from '../model/EventsIncident.pageModel'; import EventsIncidentFormPage from '../model/form/eventsIncidentForm.pageModel'; import EventsIncidentDetailsPage from '../model/EventsIncidentDetails.pageModel'; -test('Create a new relationship in incident knowledge', async ({ page }) => { +test('Create a new relationship in incident knowledge', { tag: ['@ce'] }, async ({ page }) => { const eventsIncidentPage = new EventsIncidentPage(page); const eventsIncidentForm = new EventsIncidentFormPage(page); const eventsIncidentDetailsPage = new EventsIncidentDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts b/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts index 9ee5808203e3..69d69bcfaaac 100644 --- a/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts @@ -4,7 +4,7 @@ import ExternalReferenceFormPageModel from '../model/form/externalReferenceForm. import ExternalReferencePage from '../model/externalReference.pageModel'; import ExternalReferenceDetailsPage from '../model/externalReferenceDetails.pageModel'; -test('Create a new external reference', async ({ page }) => { +test('Create a new external reference', { tag: ['@ce'] }, async ({ page }) => { const externalReferencePage = new ExternalReferencePage(page); const externalReferenceForm = new ExternalReferenceFormPageModel(page); const externalReferenceDetails = new ExternalReferenceDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts b/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts index e61e03a4374c..dae7b9932a32 100644 --- a/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from '../fixtures/baseFixtures'; import FiltersPageModel from '../model/filters.pageModel'; -test('Add a new filter in the observables list and check the filter is still present when we come back to the page', async ({ page }) => { +test('Add a new filter in the observables list and check the filter is still present when we come back to the page', { tag: ['@ce'] }, async ({ page }) => { await page.goto('/dashboard/observations/observables'); const filterUtils = new FiltersPageModel(page); await filterUtils.addEntityTypeFilter('Artifact'); diff --git a/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts b/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts index f6afc8c4b8d8..8ce550edab91 100644 --- a/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts @@ -4,7 +4,7 @@ import GroupingsPage from '../model/grouping.pageModel'; import GroupingFormPage from '../model/form/groupingForm.pageModel'; import GroupingDetailsPage from '../model/groupingDetails.pageModel'; -test('Create a new grouping', async ({ page }) => { +test('Create a new grouping', { tag: ['@ce'] }, async ({ page }) => { const groupingsPage = new GroupingsPage(page); const groupingForm = new GroupingFormPage(page); const groupingDetails = new GroupingDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts b/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts index bc62629bf023..f122d573b89f 100644 --- a/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts @@ -27,7 +27,7 @@ import EntitiesTabPageModel from '../model/EntitiesTab.pageModel'; * Delete incident response. * Check deletion. */ -test('Incident Response Creation', async ({ page }) => { +test('Incident Response Creation', { tag: ['@ce'] }, async ({ page }) => { await fakeDate(page, 'April 1 2024 12:00:00'); const leftNavigation = new LeftBarPage(page); const incidentResponsePage = new IncidentResponsePage(page); @@ -158,8 +158,8 @@ test('Incident Response Creation', async ({ page }) => { const revoked = incidentResponseDetailsPage.getTextForHeading('Revoked', 'NO'); await expect(revoked).toBeVisible(); - await expect( incidentResponseDetailsPage.overview.getLabel('campaign')).toBeVisible(); - await expect( incidentResponseDetailsPage.overview.getLabel('report')).toBeVisible(); + await expect(incidentResponseDetailsPage.overview.getLabel('campaign')).toBeVisible(); + await expect(incidentResponseDetailsPage.overview.getLabel('report')).toBeVisible(); const creators = incidentResponseDetailsPage.getTextForHeading('Creators', 'ADMIN'); await expect(creators).toBeVisible(); @@ -290,7 +290,7 @@ test('Incident Response Creation', async ({ page }) => { * Delete incident response by background task. * Check deletion. */ -test('Incident response live entities creation and relationships', async ({ page }) => { +test('Incident response live entities creation and relationships', { tag: ['@ce'] }, async ({ page }) => { const leftNavigation = new LeftBarPage(page); const toolbar = new ToolbarPageModel(page); const incidentResponsePage = new IncidentResponsePage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts b/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts index 454aa615358b..fac7f60983a0 100644 --- a/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts @@ -4,7 +4,7 @@ import InfrastructurePage from '../model/infrastructure.pageModel'; import InfrastructureFormPage from '../model/form/infrastructureForm.pageModel'; import InfrastructureDetailsPageModel from '../model/infrastructureDetails.pageModel'; -test('Create a new relationship in infrastructure knowledge', async ({ page }) => { +test('Create a new relationship in infrastructure knowledge', { tag: ['@ce'] }, async ({ page }) => { const infrastructurePage = new InfrastructurePage(page); const infrastructureForm = new InfrastructureFormPage(page); const infrastructureDetailsPage = new InfrastructureDetailsPageModel(page); diff --git a/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts b/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts index 28e190f8f50d..f9b971a35a53 100644 --- a/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts @@ -4,7 +4,7 @@ import IntrusionSetFormPage from '../model/form/intrusionSetForm.pageModel'; import IntrusionSetDetailsPage from '../model/intrusionSetDetails.pageModel'; import StixCoreRelationshipCreationFromEntityFormPage from '../model/form/stixCoreRelationshipCreationFromEntityForm.pageModel'; -test('Create a new relationship in intrusion set knowledge', async ({ page }) => { +test('Create a new relationship in intrusion set knowledge', { tag: ['@ce'] }, async ({ page }) => { const intrusionSetPage = new IntrusionSetPage(page); const intrusionSetForm = new IntrusionSetFormPage(page); const intrusionSetDetailsPage = new IntrusionSetDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts b/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts index 7ec2af84b282..343ec5f7376c 100644 --- a/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts @@ -3,7 +3,7 @@ import InvestigationDetailsPage from '../model/investigationDetails.pageModel'; import InvestigationsFormPage from '../model/form/investigationsForm.pageModel'; import InvestigationsPage from '../model/investigations.pageModel'; -test('Create a new investigations page', async ({ page }) => { +test('Create a new investigations page', { tag: ['@ce'] }, async ({ page }) => { const investigationsPage = new InvestigationsPage(page); const investigationDetailsPage = new InvestigationDetailsPage(page); const investigationsForm = new InvestigationsFormPage(page, 'Create investigation'); diff --git a/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts b/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts index 57f9e97eb53b..ff686b011bd5 100644 --- a/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from '../fixtures/baseFixtures'; import InvestigationDetailsPage from '../model/investigationDetails.pageModel'; import InvestigationsFormPage from '../model/form/investigationsForm.pageModel'; -test('Create a new investigation page and test update', async ({ page }) => { +test('Create a new investigation page and test update', { tag: ['@ce'] }, async ({ page }) => { const investigationsPage = new InvestigationsPage(page); const investigationDetailsPage = new InvestigationDetailsPage(page); const investigationsForm = new InvestigationsFormPage(page, 'Create investigation'); diff --git a/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts b/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts index f69a832776a3..19eb60e5480f 100644 --- a/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts @@ -1844,149 +1844,153 @@ const navigateAllMenu = async (page: Page) => { await leftBarPage.expectBreadcrumb('Investigations'); }; -test('Check navigation on all pages', { tag: ['@navigation'] }, async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - - // For faster debugging, each navigated can be commented. - // so they should be all independent and start from the left menu. - await navigateAllMenu(page); -}); - -test('Check navigation on Analyses menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateReports(page); - await navigateGroupings(page); - await navigateMalwareAnalyses(page); - await navigateNotes(page); - await navigateExternalReferences(page); -}); - -test('Check navigation on Cases menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateIncidentResponse(page); - await navigateRfi(page); - await navigateRft(page); - await navigateTasks(page); - await navigateFeedbacks(page); -}); - -test('Check navigation on Events menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateEventsIncident(page); - await navigateSightings(page); - await navigateObservedData(page); -}); - -test('Check navigation on Observations menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateObservables(page); - await navigateArtifact(page); - await navigateIndicators(page); - await navigateInfrastructure(page); -}); - -test('Check navigation on Threats menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateIntrusionSet(page); - await navigateCampaign(page); - await navigateThreatActorGroup(page); - await navigateThreatActorIndividual(page); -}); - -test('Check navigation on Arsenal menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateMalware(page); - await navigateChannel(page); - await navigateTool(page); - await navigateVulnerability(page); +test.describe('Navigation available on CE', { tag: ['@ce'] }, () => { + test('Check navigation on all pages', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + + // For faster debugging, each navigated can be commented. + // so they should be all independent and start from the left menu. + await navigateAllMenu(page); + }); + + test('Check navigation on Analyses menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateReports(page); + await navigateGroupings(page); + await navigateMalwareAnalyses(page); + await navigateNotes(page); + await navigateExternalReferences(page); + }); + + test('Check navigation on Cases menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateIncidentResponse(page); + await navigateRfi(page); + await navigateRft(page); + await navigateTasks(page); + await navigateFeedbacks(page); + }); + + test('Check navigation on Events menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateEventsIncident(page); + await navigateSightings(page); + await navigateObservedData(page); + }); + + test('Check navigation on Observations menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateObservables(page); + await navigateArtifact(page); + await navigateIndicators(page); + await navigateInfrastructure(page); + }); + + test('Check navigation on Threats menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateIntrusionSet(page); + await navigateCampaign(page); + await navigateThreatActorGroup(page); + await navigateThreatActorIndividual(page); + }); + + test('Check navigation on Arsenal menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateMalware(page); + await navigateChannel(page); + await navigateTool(page); + await navigateVulnerability(page); + }); + + test('Check navigation on Techniques menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateAttackPattern(page); + await navigateNarrative(page); + await navigateCourseOfAction(page); + await navigateDataComponent(page); + await navigateDataSource(page); + }); + + test('Check navigation on Entities menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateSector(page); + await navigateEvent(page); + await navigateOrganization(page); + await navigateSecurityPlatform(page); + await navigateSystem(page); + await navigateIndividual(page); + }); + + test('Check navigation on Locations menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateRegion(page); + await navigateCountry(page); + await navigateAdministrativeArea(page); + await navigateCity(page); + await navigatePosition(page); + }); + + test.skip('Check navigation on Data menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateDataEntities(page); + await navigateDataRelationships(page); + await navigateIngestion(page); + await navigateDataImport(page); + await navigateProcessing(page); + await navigateDataSharing(page); + await navigateDataManagement(page); + }); + + test('Check navigation on Trash menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateTrash(page); + }); }); -test('Check navigation on Techniques menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateAttackPattern(page); - await navigateNarrative(page); - await navigateCourseOfAction(page); - await navigateDataComponent(page); - await navigateDataSource(page); -}); - -test('Check navigation on Entities menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateSector(page); - await navigateEvent(page); - await navigateOrganization(page); - await navigateSecurityPlatform(page); - await navigateSystem(page); - await navigateIndividual(page); -}); - -test('Check navigation on Locations menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateRegion(page); - await navigateCountry(page); - await navigateAdministrativeArea(page); - await navigateCity(page); - await navigatePosition(page); -}); - -test.skip('Check navigation on Data menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateDataEntities(page); - await navigateDataRelationships(page); - await navigateIngestion(page); - await navigateDataImport(page); - await navigateProcessing(page); - await navigateDataSharing(page); - await navigateDataManagement(page); -}); - -test('Check navigation on Trash menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateTrash(page); -}); - -// Separating settings menu in two to avoid timeout while testing as this part is too long - -test('Check navigation on Settings menu part one', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateSettings(page); - await navigateSecurity(page); - await navigateCustomization(page); - await navigateTaxonomies(page); -}); - -test('Check navigation on Settings menu part two', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateSettings(page); - await navigateActivity(page); - await navigateFileIndexing(page); - await navigateExperience(page); +test.describe('Navigation available only EE', { tag: ['@ee'] }, () => { + // Separating settings menu in two to avoid timeout while testing as this part is too long + + test('Check navigation on Settings menu part one', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateSettings(page); + await navigateSecurity(page); + await navigateCustomization(page); + await navigateTaxonomies(page); + }); + + test('Check navigation on Settings menu part two', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateSettings(page); + await navigateActivity(page); + await navigateFileIndexing(page); + await navigateExperience(page); + }); }); diff --git a/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts b/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts index 26f887083af1..2380c7744e31 100644 --- a/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts @@ -3,7 +3,7 @@ import NotesPage from '../model/note.pageModel'; import NoteFormPage from '../model/form/noteForm.pageModel'; import NoteDetailsPage from '../model/noteDetails.pageModel'; -test('Create a new note', async ({ page }) => { +test('Create a new note', { tag: ['@ce'] }, async ({ page }) => { const notesPage = new NotesPage(page); const noteForm = new NoteFormPage(page); const noteDetails = new NoteDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts b/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts index 3b9b32ffd20c..4f3590fa897e 100644 --- a/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts @@ -15,7 +15,7 @@ import { awaitUntilCondition } from '../utils'; * Test flag entities * Delete PIR */ -test('Pir CRUD', { tag: ['@pir', '@mutation'] }, async ({ page, request }) => { +test('Pir CRUD', { tag: ['@pir', '@mutation', '@ee'] }, async ({ page, request }) => { const leftNavigation = new LeftBarPage(page); const pirPage = new PirPage(page); const pirForm = new PirFormPageModel(page); diff --git a/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts b/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts index c9f3e412687f..1610ebea61f7 100644 --- a/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts @@ -27,7 +27,7 @@ import EntitiesTabPageModel from '../model/EntitiesTab.pageModel'; * Delete report. * Check deletion. */ -test('Report CRUD', { tag: ['@report', '@knowledge', '@mutation'] }, async ({ page }) => { +test('Report CRUD', { tag: ['@report', '@knowledge', '@mutation', '@ce'] }, async ({ page }) => { await fakeDate(page, 'April 1 2024 12:00:00'); const leftNavigation = new LeftBarPage(page); const reportPage = new ReportPage(page); @@ -301,7 +301,7 @@ test('Report CRUD', { tag: ['@report', '@knowledge', '@mutation'] }, async ({ pa * Delete report by background task. * Check deletion. */ -test('Report live entities creation and relationships', { tag: ['@report', '@knowledge', '@mutation'] }, async ({ page }) => { +test('Report live entities creation and relationships', { tag: ['@report', '@knowledge', '@mutation', '@ce'] }, async ({ page }) => { const leftNavigation = new LeftBarPage(page); // const toolbar = new ToolbarPageModel(page); const reportPage = new ReportPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts b/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts index e0bddb843d4b..ed92fca0ec2d 100644 --- a/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts @@ -6,7 +6,7 @@ import CaseRfiFormPage from '../model/form/caseRfiForm.pageModel'; import CaseRfiDetailsPage from '../model/caseRfiDetails.pageModel'; import ParticipantsFormPage from '../model/form/participantsForm.pageModel'; -test('Request for information CRUD', async ({ page }) => { +test('Request for information CRUD', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); const caseRfiPage = new CaseRfiPage(page); const caseRfiDetailsPage = new CaseRfiDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts b/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts index 712feeb62d62..c34229257a81 100644 --- a/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts @@ -5,7 +5,7 @@ import CaseRftPage from '../model/caseRft.pageModel'; import CaseRftDetailsPage from '../model/caseRftDetails.pageModel'; import CaseRftFormPage from '../model/form/caseRftForm.pageModel'; -test('Request for takedown CRUD', async ({ page }) => { +test('Request for takedown CRUD', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); const caseRftPage = new CaseRftPage(page); const caseRftDetailsPage = new CaseRftDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts b/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts index 08cdc2447ed0..6cde1dc042bc 100644 --- a/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts @@ -3,7 +3,7 @@ import ReportPage from '../model/report.pageModel'; import { expect, test } from '../fixtures/baseFixtures'; import SearchPageModel from '../model/search.pageModel'; -test('Testing content customization for Report', async ({ page }) => { +test('Testing content customization for Report', { tag: ['@ce'] }, async ({ page }) => { await page.goto('/'); const leftBarPage = new LeftBarPage(page); const reportPage = new ReportPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts b/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts index 1c7bff389748..1d8535bb6c19 100644 --- a/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts @@ -12,7 +12,7 @@ const openThemeEditMenu = async (themeName: string, page: Page) => { .click(); }; -test('Check Logo replacement', async ({ page }) => { +test('Check Logo replacement', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); await page.goto('/'); diff --git a/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts b/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts index cd700f2b3cfe..5ecfd8e55692 100644 --- a/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts @@ -18,7 +18,7 @@ const openThemeEditMenu = async (themeName: string, page: Page) => { * MUST edit custom theme. * MUST delete custom theme. */ -test('Custom theme creation, edition, and deletion', async ({ page }) => { +test('Custom theme creation, edition, and deletion', { tag: ['@ce'] }, async ({ page }) => { const THEME = { name: `${new Date().toISOString()} Test Theme`, theme_background: '#e72a2a', @@ -41,7 +41,7 @@ test('Custom theme creation, edition, and deletion', async ({ page }) => { await page.getByTestId('create-theme-btn').click(); // disable on purpose because we want that fill to be sequentially - /* eslint-disable no-await-in-loop */ + for (const [key, value] of Object.entries(THEME)) { await page.locator(`input[name="${key}"]`).fill(value); } @@ -93,7 +93,7 @@ test('Custom theme creation, edition, and deletion', async ({ page }) => { /** * MUST ensure cannot delete system theme. */ -test('Cannot delete system theme', async ({ page }) => { +test('Cannot delete system theme', { tag: ['@ce'] }, async ({ page }) => { // Navigate to Settings const leftBarPage = new LeftBarPage(page); await page.goto('/'); diff --git a/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts b/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts index a86144c37e58..d5a2257e2125 100644 --- a/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts @@ -3,7 +3,7 @@ import TopMenuProfilePage from './model/menu/topMenuProfile.pageModel'; import LoginFormPageModel from './model/form/loginForm.pageModel'; import DashboardPage from './model/dashboard.pageModel'; -test('test logout', async ({ page }) => { +test('test logout', { tag: ['@ce'] }, async ({ page }) => { const loginPage = new LoginFormPageModel(page); const topMenu = new TopMenuProfilePage(page); const dashboardPage = new DashboardPage(page); diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 8df86446e1bc..e471f93dc22d 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -5,7 +5,7 @@ __metadata: version: 8 cacheKey: 10c0 -"3d-force-graph@npm:^1.73": +"3d-force-graph@npm:^1.79": version: 1.79.0 resolution: "3d-force-graph@npm:1.79.0" dependencies: @@ -19,9 +19,9 @@ __metadata: linkType: hard "@acemir/cssom@npm:^0.9.28": - version: 0.9.29 - resolution: "@acemir/cssom@npm:0.9.29" - checksum: 10c0/029fdaf1f7e6e9e6267ba0d94301de15001066cc729956851cedfe486a82e6d925eb8fc8f7f488e6618ce0a615ae9a5e660d9ab13406c8396e2852df5bcbbdd6 + version: 0.9.31 + resolution: "@acemir/cssom@npm:0.9.31" + checksum: 10c0/cbfff98812642104ec3b37de1ad3a53f216ddc437e7b9276a23f46f2453844ea3c3f46c200bc4656a2f747fb26567560b3cc5183d549d119a758926551b5f566 languageName: node linkType: hard @@ -106,16 +106,16 @@ __metadata: languageName: node linkType: hard -"@asamuzakjp/css-color@npm:^4.1.0": - version: 4.1.0 - resolution: "@asamuzakjp/css-color@npm:4.1.0" +"@asamuzakjp/css-color@npm:^4.1.1": + version: 4.1.1 + resolution: "@asamuzakjp/css-color@npm:4.1.1" dependencies: "@csstools/css-calc": "npm:^2.1.4" "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - lru-cache: "npm:^11.2.2" - checksum: 10c0/097b9270a5befb765885dda43d6914ccbaa575565525d307e8ba3ba07f98e466062f4a77b9657e65160db9110c41c59cf5a6937479647f73637aeddf5c421579 + lru-cache: "npm:^11.2.4" + checksum: 10c0/2948ae9cd4c2f326ab5470d6ac7d415bb8062150ef254f830d774b6a77d6dccfbdb4b84ed4ef5c86c5643d42c52d77204b8d94d0d90f2e2cea9ec9b6cbb9c336 languageName: node linkType: hard @@ -139,70 +139,70 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/code-frame@npm:7.28.6" dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + checksum: 10c0/ed5d57f99455e3b1c23e75ebb8430c6b9800b4ecd0121b4348b97cecb65406a47778d6db61f0d538a4958bb01b4b277e90348a68d39bd3beff1d7c940ed6dd66 languageName: node linkType: hard -"@babel/compat-data@npm:^7.27.2": - version: 7.28.5 - resolution: "@babel/compat-data@npm:7.28.5" - checksum: 10c0/702a25de73087b0eba325c1d10979eed7c9b6662677386ba7b5aa6eace0fc0676f78343bae080a0176ae26f58bd5535d73b9d0fbb547fef377692e8b249353a7 +"@babel/compat-data@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/compat-data@npm:7.28.6" + checksum: 10c0/2d047431041281eaf33e9943d1a269d3374dbc9b498cafe6a18f5ee9aee7bb96f7f6cac0304eab4d13c41fc4db00fe4ca16c7aa44469ca6a211b8b6343b78fc4 languageName: node linkType: hard "@babel/core@npm:^7.23.5, @babel/core@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/core@npm:7.28.5" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.28.3" - "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" + version: 7.28.6 + resolution: "@babel/core@npm:7.28.6" + dependencies: + "@babel/code-frame": "npm:^7.28.6" + "@babel/generator": "npm:^7.28.6" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helpers": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 + checksum: 10c0/716b88b1ab057aa53ffa40f2b2fb7e4ab7a35cd6a065fa60e55ca13d2a666672592329f7ea9269aec17e90cc7ce29f42eda566d07859bfd998329a9f283faadb languageName: node linkType: hard -"@babel/generator@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/generator@npm:7.28.5" +"@babel/generator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/generator@npm:7.28.6" dependencies: - "@babel/parser": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + checksum: 10c0/162fa358484a9a18e8da1235d998f10ea77c63bab408c8d3e327d5833f120631a77ff022c5ed1d838ee00523f8bb75df1f08196d3657d0bca9f2cfeb8503cc12 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/helper-compilation-targets@npm:7.27.2" +"@babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" dependencies: - "@babel/compat-data": "npm:^7.27.2" + "@babel/compat-data": "npm:^7.28.6" "@babel/helper-validator-option": "npm:^7.27.1" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 languageName: node linkType: hard @@ -213,33 +213,33 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-module-imports@npm:7.27.1" +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" dependencies: - "@babel/traverse": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/helper-module-transforms@npm:7.28.3" +"@babel/helper-module-transforms@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-transforms@npm:7.28.6" dependencies: - "@babel/helper-module-imports": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.3" + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-validator-identifier": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + checksum: 10c0/6f03e14fc30b287ce0b839474b5f271e72837d0cafe6b172d759184d998fbee3903a035e81e07c2c596449e504f453463d58baa65b6f40a37ded5bec74620b2b languageName: node linkType: hard "@babel/helper-plugin-utils@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-plugin-utils@npm:7.27.1" - checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b + version: 7.28.6 + resolution: "@babel/helper-plugin-utils@npm:7.28.6" + checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d languageName: node linkType: hard @@ -250,7 +250,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": +"@babel/helper-validator-identifier@npm:^7.28.5": version: 7.28.5 resolution: "@babel/helper-validator-identifier@npm:7.28.5" checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 @@ -264,24 +264,24 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/helpers@npm:7.28.4" +"@babel/helpers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helpers@npm:7.28.6" dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" - checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/c4a779c66396bb0cf619402d92f1610601ff3832db2d3b86b9c9dd10983bf79502270e97ac6d5280cea1b1a37de2f06ecbac561bd2271545270407fbe64027cb languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/parser@npm:7.28.5" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/parser@npm:7.28.6" dependencies: - "@babel/types": "npm:^7.28.5" + "@babel/types": "npm:^7.28.6" bin: parser: ./bin/babel-parser.js - checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + checksum: 10c0/d6bfe8aa8e067ef58909e9905496157312372ca65d8d2a4f2b40afbea48d59250163755bba8ae626a615da53d192b084bcfc8c9dad8b01e315b96967600de581 languageName: node linkType: hard @@ -308,45 +308,45 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.26.7, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7": - version: 7.28.4 - resolution: "@babel/runtime@npm:7.28.4" - checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 + version: 7.28.6 + resolution: "@babel/runtime@npm:7.28.6" + checksum: 10c0/358cf2429992ac1c466df1a21c1601d595c46930a13c1d4662fde908d44ee78ec3c183aaff513ecb01ef8c55c3624afe0309eeeb34715672dbfadb7feedb2c0d languageName: node linkType: hard -"@babel/template@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/template@npm:7.27.2" +"@babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/parser": "npm:^7.27.2" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 languageName: node linkType: hard -"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/traverse@npm:7.28.5" +"@babel/traverse@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/traverse@npm:7.28.6" dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" + "@babel/code-frame": "npm:^7.28.6" + "@babel/generator": "npm:^7.28.6" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.5" + "@babel/parser": "npm:^7.28.6" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" debug: "npm:^4.3.1" - checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + checksum: 10c0/ed5deb9c3f03e2d1ad2d44b9c92c84cce24593245c3f7871ce27ee1b36d98034e6cd895fa98a94eb44ebabe1d22f51b10b09432939d1c51a0fcaab98f17a97bc languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/types@npm:7.28.5" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/types@npm:7.28.6" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + checksum: 10c0/54a6a9813e48ef6f35aa73c03b3c1572cad7fa32b61b35dd07e4230bc77b559194519c8a4d8106a041a27cc7a94052579e238a30a32d5509aa4da4d6fd83d990 languageName: node linkType: hard @@ -1166,12 +1166,10 @@ __metadata: languageName: node linkType: hard -"@csstools/css-syntax-patches-for-csstree@npm:1.0.14": - version: 1.0.14 - resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/e431cf5aa4ccd6a40f4a417663ac7178c822c5427b9c8473e466257dc46dd9698e3852d5517ec220b7d1d1ea911e9007ecb429464329ae169a0aa68b56f1c3ac +"@csstools/css-syntax-patches-for-csstree@npm:^1.0.21": + version: 1.0.25 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.25" + checksum: 10c0/a24229cc44cd64642ba76c73f59e7b0b00cfaffd992bf515d32f266aa68b983d9a945ebf8d45c90671d1e2c775a225d8b6257b01fdfeb6602c0c1f7e3faf77c0 languageName: node linkType: hard @@ -1204,21 +1202,21 @@ __metadata: linkType: hard "@emnapi/core@npm:^1.4.3": - version: 1.7.1 - resolution: "@emnapi/core@npm:1.7.1" + version: 1.8.1 + resolution: "@emnapi/core@npm:1.8.1" dependencies: "@emnapi/wasi-threads": "npm:1.1.0" tslib: "npm:^2.4.0" - checksum: 10c0/f3740be23440b439333e3ae3832163f60c96c4e35337f3220ceba88f36ee89a57a871d27c94eb7a9ff98a09911ed9a2089e477ab549f4d30029f8b907f84a351 + checksum: 10c0/2c242f4b49779bac403e1cbcc98edacdb1c8ad36562408ba9a20663824669e930bc8493be46a2522d9dc946b8d96cd7073970bae914928c7671b5221c85b432e languageName: node linkType: hard "@emnapi/runtime@npm:^1.4.3": - version: 1.7.1 - resolution: "@emnapi/runtime@npm:1.7.1" + version: 1.8.1 + resolution: "@emnapi/runtime@npm:1.8.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5 + checksum: 10c0/f4929d75e37aafb24da77d2f58816761fe3f826aad2e37fa6d4421dac9060cbd5098eea1ac3c9ecc4526b89deb58153852fa432f87021dc57863f2ff726d713f languageName: node linkType: hard @@ -1393,13 +1391,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/aix-ppc64@npm:0.25.12" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/aix-ppc64@npm:0.27.2" @@ -1407,13 +1398,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm64@npm:0.25.12" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm64@npm:0.27.2" @@ -1421,13 +1405,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm@npm:0.25.12" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm@npm:0.27.2" @@ -1435,13 +1412,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-x64@npm:0.25.12" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-x64@npm:0.27.2" @@ -1449,13 +1419,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-arm64@npm:0.25.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-arm64@npm:0.27.2" @@ -1463,13 +1426,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-x64@npm:0.25.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-x64@npm:0.27.2" @@ -1477,13 +1433,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-arm64@npm:0.25.12" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-arm64@npm:0.27.2" @@ -1491,13 +1440,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-x64@npm:0.25.12" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-x64@npm:0.27.2" @@ -1505,13 +1447,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm64@npm:0.25.12" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm64@npm:0.27.2" @@ -1519,13 +1454,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm@npm:0.25.12" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm@npm:0.27.2" @@ -1533,13 +1461,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ia32@npm:0.25.12" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ia32@npm:0.27.2" @@ -1547,13 +1468,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-loong64@npm:0.25.12" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-loong64@npm:0.27.2" @@ -1561,13 +1475,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-mips64el@npm:0.25.12" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-mips64el@npm:0.27.2" @@ -1575,13 +1482,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ppc64@npm:0.25.12" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ppc64@npm:0.27.2" @@ -1589,13 +1489,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-riscv64@npm:0.25.12" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-riscv64@npm:0.27.2" @@ -1603,13 +1496,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-s390x@npm:0.25.12" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-s390x@npm:0.27.2" @@ -1617,13 +1503,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-x64@npm:0.25.12" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-x64@npm:0.27.2" @@ -1631,13 +1510,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-arm64@npm:0.25.12" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-arm64@npm:0.27.2" @@ -1645,13 +1517,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-x64@npm:0.25.12" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-x64@npm:0.27.2" @@ -1659,13 +1524,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-arm64@npm:0.25.12" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-arm64@npm:0.27.2" @@ -1673,13 +1531,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-x64@npm:0.25.12" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-x64@npm:0.27.2" @@ -1687,13 +1538,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openharmony-arm64@npm:0.25.12" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openharmony-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openharmony-arm64@npm:0.27.2" @@ -1701,13 +1545,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/sunos-x64@npm:0.25.12" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/sunos-x64@npm:0.27.2" @@ -1715,13 +1552,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-arm64@npm:0.25.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-arm64@npm:0.27.2" @@ -1729,13 +1559,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-ia32@npm:0.25.12" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-ia32@npm:0.27.2" @@ -1743,13 +1566,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-x64@npm:0.25.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-x64@npm:0.27.2" @@ -1757,18 +1573,18 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.0": - version: 4.9.0 - resolution: "@eslint-community/eslint-utils@npm:4.9.0" +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": +"@eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.12.2": version: 4.12.2 resolution: "@eslint-community/regexpp@npm:4.12.2" checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d @@ -1845,10 +1661,22 @@ __metadata: languageName: node linkType: hard -"@faker-js/faker@npm:10.1.0": - version: 10.1.0 - resolution: "@faker-js/faker@npm:10.1.0" - checksum: 10c0/3dc277245ec1bef8b839a9dc45b4ccb1688d21b8bde6531a849c1773193de30047e40e085b7e900c42c940911ecf1645b582ad3c85093df5bfdbddeb44a2b554 +"@exodus/bytes@npm:^1.6.0": + version: 1.8.0 + resolution: "@exodus/bytes@npm:1.8.0" + peerDependencies: + "@exodus/crypto": ^1.0.0-rc.4 + peerDependenciesMeta: + "@exodus/crypto": + optional: true + checksum: 10c0/1878868519230fa564b80d3d12fa7e2b559dfed143f4e48969eb5f98083caea431bbc70356d6b2f90ba80c8148295fe85af6a5ed8a746dd53baefe4f1ed086e8 + languageName: node + linkType: hard + +"@faker-js/faker@npm:10.2.0": + version: 10.2.0 + resolution: "@faker-js/faker@npm:10.2.0" + checksum: 10c0/72b5048ff32e90b2876560015e177f91b0625dafaaa43631302e413329105be331a1c126c8f7b0f95817cacd51c9222b58e62b6f09f5485522302e6103f19f09 languageName: node linkType: hard @@ -1862,9 +1690,9 @@ __metadata: languageName: node linkType: hard -"@filigran/chatbot@npm:1.0.2": - version: 1.0.2 - resolution: "@filigran/chatbot@npm:1.0.2" +"@filigran/chatbot@npm:1.1.0": + version: 1.1.0 + resolution: "@filigran/chatbot@npm:1.1.0" dependencies: "@microsoft/fetch-event-source": "npm:2.0.1" "@ts-stack/markdown": "npm:1.5.0" @@ -1873,7 +1701,7 @@ __metadata: solid-element: "npm:1.9.1" solid-js: "npm:1.9.10" uuid: "npm:13.0.0" - checksum: 10c0/3ee0f9561b3905ebc7691a5dd0ef7ea84d321a9684f42acb418e8a03baa569ae48560d4ace305229351772f95118f663761baaaea8fc96e14e7f92cce2e81d7b + checksum: 10c0/3f808a7a24d0758b61693eb4e2d88be9cfdce557dd4dd8202ad460610fbc8251219e33ecce17cbcf9b370efafc25d93a728a7514e7a0b7da073af0a0a714b69c languageName: node linkType: hard @@ -2187,12 +2015,12 @@ __metadata: linkType: hard "@grpc/grpc-js@npm:^1.12.6": - version: 1.14.2 - resolution: "@grpc/grpc-js@npm:1.14.2" + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" dependencies: "@grpc/proto-loader": "npm:^0.8.0" "@js-sdsl/ordered-map": "npm:^4.4.2" - checksum: 10c0/12e7b885cd5033db53fc14099b7ada55d82a7e358fece4d4a39a8520885332f871353fd1630828e77c4777f2ec6b99531b087263298482b7b9c5cf20e540d371 + checksum: 10c0/f41f06a311b93cca8c472d56e21387e0f7b57bb2337a91d15ea4279bac8ec4fa0de6bd0d881201229ab800c0f0c55277911ecb850e057f20a828d0ddd623551d languageName: node linkType: hard @@ -2365,7 +2193,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": version: 0.3.31 resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: @@ -2772,9 +2600,9 @@ __metadata: languageName: node linkType: hard -"@mui/types@npm:^7.4.9": - version: 7.4.9 - resolution: "@mui/types@npm:7.4.9" +"@mui/types@npm:^7.4.10": + version: 7.4.10 + resolution: "@mui/types@npm:7.4.10" dependencies: "@babel/runtime": "npm:^7.28.4" peerDependencies: @@ -2782,7 +2610,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/ed371a08af12a712fb5985547162b99fc58b5d451ea1101927a6d2f2c005087eb25603553d2c4242ee3a34359333b917432052148c93ed7dded5d80042fec504 + checksum: 10c0/2e1e807795dcb6f5bdb62eb49068a7f4414299c62f55ceaaa05925a1d043799216150873c00c02f086fd631f7171c97ea416dc66c099c98649503ee3046dab3d languageName: node linkType: hard @@ -2799,22 +2627,22 @@ __metadata: linkType: hard "@mui/utils@npm:^5.16.6 || ^6.0.0 || ^7.0.0": - version: 7.3.6 - resolution: "@mui/utils@npm:7.3.6" + version: 7.3.7 + resolution: "@mui/utils@npm:7.3.7" dependencies: "@babel/runtime": "npm:^7.28.4" - "@mui/types": "npm:^7.4.9" + "@mui/types": "npm:^7.4.10" "@types/prop-types": "npm:^15.7.15" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" - react-is: "npm:^19.2.0" + react-is: "npm:^19.2.3" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/0af5f65a022028fa25b6a331443c44fdfd9060355495fd10849807f7aee9c9a4f776592cfe427b3c478f4ee4bf96349d9e84d1a035d5e31ba84d821748b4f185 + checksum: 10c0/2732a01a24968c8fe73b1cf3c7afabffd8a5f13556f3f8078529eaf09d855a05cb7905421b733cb671f771406eb857f5dddb3b82166ecc2a3d0ab1a987954d08 languageName: node linkType: hard @@ -2925,90 +2753,98 @@ __metadata: languageName: node linkType: hard -"@napi-rs/canvas-android-arm64@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-android-arm64@npm:0.1.83" +"@napi-rs/canvas-android-arm64@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-android-arm64@npm:0.1.88" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@napi-rs/canvas-darwin-arm64@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.83" +"@napi-rs/canvas-darwin-arm64@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.88" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@napi-rs/canvas-darwin-x64@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.83" +"@napi-rs/canvas-darwin-x64@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.88" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.83" +"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.88" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.83" +"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.88" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@napi-rs/canvas-linux-arm64-musl@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.83" +"@napi-rs/canvas-linux-arm64-musl@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.88" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.83" +"@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.88" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@napi-rs/canvas-linux-x64-gnu@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.83" +"@napi-rs/canvas-linux-x64-gnu@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.88" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@napi-rs/canvas-linux-x64-musl@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.83" +"@napi-rs/canvas-linux-x64-musl@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.88" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@napi-rs/canvas-win32-x64-msvc@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.83" +"@napi-rs/canvas-win32-arm64-msvc@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-win32-arm64-msvc@npm:0.1.88" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-win32-x64-msvc@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.88" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@napi-rs/canvas@npm:^0.1.80": - version: 0.1.83 - resolution: "@napi-rs/canvas@npm:0.1.83" - dependencies: - "@napi-rs/canvas-android-arm64": "npm:0.1.83" - "@napi-rs/canvas-darwin-arm64": "npm:0.1.83" - "@napi-rs/canvas-darwin-x64": "npm:0.1.83" - "@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.83" - "@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.83" - "@napi-rs/canvas-linux-arm64-musl": "npm:0.1.83" - "@napi-rs/canvas-linux-riscv64-gnu": "npm:0.1.83" - "@napi-rs/canvas-linux-x64-gnu": "npm:0.1.83" - "@napi-rs/canvas-linux-x64-musl": "npm:0.1.83" - "@napi-rs/canvas-win32-x64-msvc": "npm:0.1.83" + version: 0.1.88 + resolution: "@napi-rs/canvas@npm:0.1.88" + dependencies: + "@napi-rs/canvas-android-arm64": "npm:0.1.88" + "@napi-rs/canvas-darwin-arm64": "npm:0.1.88" + "@napi-rs/canvas-darwin-x64": "npm:0.1.88" + "@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.88" + "@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.88" + "@napi-rs/canvas-linux-arm64-musl": "npm:0.1.88" + "@napi-rs/canvas-linux-riscv64-gnu": "npm:0.1.88" + "@napi-rs/canvas-linux-x64-gnu": "npm:0.1.88" + "@napi-rs/canvas-linux-x64-musl": "npm:0.1.88" + "@napi-rs/canvas-win32-arm64-msvc": "npm:0.1.88" + "@napi-rs/canvas-win32-x64-msvc": "npm:0.1.88" dependenciesMeta: "@napi-rs/canvas-android-arm64": optional: true @@ -3028,9 +2864,11 @@ __metadata: optional: true "@napi-rs/canvas-linux-x64-musl": optional: true + "@napi-rs/canvas-win32-arm64-msvc": + optional: true "@napi-rs/canvas-win32-x64-msvc": optional: true - checksum: 10c0/fb7abc2823fe14f85039edda11a6f98aa5dfa07a0f77799f4e053345ad7c99d7700670d4df7f3d6203a90b17cb2d85ea1361d8acbe54da846eb7fb7bcaf62a7a + checksum: 10c0/00543b86c66b0b12aa0c60922099ae69d83379d8a47345c1c6d079a37788dc1c6231b64d72dca20a581d817121efa0a37c965a71ec96d248c91a4627872753d3 languageName: node linkType: hard @@ -3767,34 +3605,34 @@ __metadata: linkType: hard "@react-aria/focus@npm:^3.20.2": - version: 3.21.2 - resolution: "@react-aria/focus@npm:3.21.2" + version: 3.21.3 + resolution: "@react-aria/focus@npm:3.21.3" dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" + "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/utils": "npm:^3.32.0" "@react-types/shared": "npm:^3.32.1" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/bfcdbb8d47bf038c035b025df6b9c292eeea9a2af7c77ec2ac27c302cb64dc481cfe80bb6575b399301ad1516feba134dec01e3c112ca2cf912ca13b47965917 + checksum: 10c0/c1169f2047908dd2641439ed49b51d1482df00514f5adc569d73727bc6375150198dd1b6e345a79fc31f3571d7d09549743ba2e6b3168ed8d6a554708d48fa9b languageName: node linkType: hard -"@react-aria/interactions@npm:^3.25.0, @react-aria/interactions@npm:^3.25.6": - version: 3.25.6 - resolution: "@react-aria/interactions@npm:3.25.6" +"@react-aria/interactions@npm:^3.25.0, @react-aria/interactions@npm:^3.26.0": + version: 3.26.0 + resolution: "@react-aria/interactions@npm:3.26.0" dependencies: "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" + "@react-aria/utils": "npm:^3.32.0" "@react-stately/flags": "npm:^3.1.2" "@react-types/shared": "npm:^3.32.1" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/000300ee3cfab724228c89f7261e94e1357f91f746256c352466a014ab6e1e907a3e6c6a2c0e73a6dd7efc97c1a608c96462de5b41a3eebda22cbc97550a797d + checksum: 10c0/542044d08c02aec337ceda1ed55e5b01f6fa3e76c930b0063bc4a2146102d39659df81570912b7bef4782e268c08bbfdca82a44df413ec8ce8f1bdf930e97051 languageName: node linkType: hard @@ -3809,20 +3647,20 @@ __metadata: languageName: node linkType: hard -"@react-aria/utils@npm:^3.31.0": - version: 3.31.0 - resolution: "@react-aria/utils@npm:3.31.0" +"@react-aria/utils@npm:^3.32.0": + version: 3.32.0 + resolution: "@react-aria/utils@npm:3.32.0" dependencies: "@react-aria/ssr": "npm:^3.9.10" "@react-stately/flags": "npm:^3.1.2" - "@react-stately/utils": "npm:^3.10.8" + "@react-stately/utils": "npm:^3.11.0" "@react-types/shared": "npm:^3.32.1" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/a6b5c6b85a51fa9ca204f045f70d36a55e16b56b85141d556eaacb7b74c4c0915189f6d2baea06df59bdd2926dcca08c2313c98478dbb50ed8e59f9b6754735c + checksum: 10c0/10fd9b162f8c752bf70070f5e091eaf3bd2c163b0a86e1f29c306c766b6b1acbbefa85c1ed6c28973b858afeafd638faa783361440c679890698c3d78bb50121 languageName: node linkType: hard @@ -3846,14 +3684,14 @@ __metadata: languageName: node linkType: hard -"@react-stately/utils@npm:^3.10.8": - version: 3.10.8 - resolution: "@react-stately/utils@npm:3.10.8" +"@react-stately/utils@npm:^3.11.0": + version: 3.11.0 + resolution: "@react-stately/utils@npm:3.11.0" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/a97cc292986e3eeb2ceb1626671ce60e8342a3ff35ab92bcfcb94bd6b28729836cc592e3fe4df2fba603e5fdd26291be77b7f60441920298c282bb93f424feba + checksum: 10c0/09b38438df19fd8ff14d3147b2f9e5d42869b3ee637b0e33d6f2ab5cba93612e640c6de339b766b8c825d7bef828851fd551d5a197a037eb1331913546b8516c languageName: node linkType: hard @@ -3963,8 +3801,8 @@ __metadata: linkType: hard "@reduxjs/toolkit@npm:1.x.x || 2.x.x": - version: 2.11.0 - resolution: "@reduxjs/toolkit@npm:2.11.0" + version: 2.11.2 + resolution: "@reduxjs/toolkit@npm:2.11.2" dependencies: "@standard-schema/spec": "npm:^1.0.0" "@standard-schema/utils": "npm:^0.3.0" @@ -3980,14 +3818,14 @@ __metadata: optional: true react-redux: optional: true - checksum: 10c0/8dce17761d289ccdcc7599cabfe3a2f28eaa13e2971ced8f9bd3160af31d96ea11ddecfcbe9fe0c07b5a9fba592140a202d4581cb9aa1c486c5ba0e01316dfce + checksum: 10c0/4d388b96dc4b12a577af23607c252b3647c1b3b5136dbb0212e1dbbef9bb309e93d3ba6a95795ee165e87e4286453025cd67a98b5b3bb6d244b93ea487dd1ac0 languageName: node linkType: hard -"@remix-run/router@npm:1.23.1": - version: 1.23.1 - resolution: "@remix-run/router@npm:1.23.1" - checksum: 10c0/94ac9632c0070199b8275cd6dffe78eb4c02e8926328937c65561c5c30d7ddf842743df3c8f7df302f00a593dd204846d93667fbbbe3c3641437d7b8f333ed90 +"@remix-run/router@npm:1.23.2": + version: 1.23.2 + resolution: "@remix-run/router@npm:1.23.2" + checksum: 10c0/7096b7f2086b2cd80c9e06873b71a8317e04858c01edc06a6fed187b660408a90f47c8e120e8af4c369cf1fa6b6a316a66b0917f42b6eb8a566e98b277c50449 languageName: node linkType: hard @@ -4043,156 +3881,177 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" +"@rollup/rollup-android-arm-eabi@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.55.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm64@npm:4.53.3" +"@rollup/rollup-android-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-android-arm64@npm:4.55.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" +"@rollup/rollup-darwin-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.55.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" +"@rollup/rollup-darwin-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.55.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" +"@rollup/rollup-freebsd-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.55.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" +"@rollup/rollup-freebsd-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.55.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.55.1" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-musleabihf@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.55.1" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" +"@rollup/rollup-linux-arm64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.55.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" +"@rollup/rollup-linux-arm64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.55.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" +"@rollup/rollup-linux-loong64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.55.1" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" +"@rollup/rollup-linux-loong64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.55.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.55.1" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" +"@rollup/rollup-linux-ppc64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.55.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.55.1" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" +"@rollup/rollup-linux-riscv64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.55.1" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" +"@rollup/rollup-linux-s390x-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.55.1" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" +"@rollup/rollup-linux-x64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.55.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" +"@rollup/rollup-linux-x64-musl@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.55.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" +"@rollup/rollup-openbsd-x64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.55.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.55.1" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" +"@rollup/rollup-win32-arm64-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.55.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" +"@rollup/rollup-win32-ia32-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.55.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" +"@rollup/rollup-win32-x64-gnu@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.55.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" +"@rollup/rollup-win32-x64-msvc@npm:4.55.1": + version: 4.55.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.55.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4205,9 +4064,9 @@ __metadata: linkType: hard "@standard-schema/spec@npm:^1.0.0": - version: 1.0.0 - resolution: "@standard-schema/spec@npm:1.0.0" - checksum: 10c0/a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 languageName: node linkType: hard @@ -4218,19 +4077,19 @@ __metadata: languageName: node linkType: hard -"@stylistic/eslint-plugin@npm:5.6.1": - version: 5.6.1 - resolution: "@stylistic/eslint-plugin@npm:5.6.1" +"@stylistic/eslint-plugin@npm:5.7.0": + version: 5.7.0 + resolution: "@stylistic/eslint-plugin@npm:5.7.0" dependencies: - "@eslint-community/eslint-utils": "npm:^4.9.0" - "@typescript-eslint/types": "npm:^8.47.0" - eslint-visitor-keys: "npm:^4.2.1" - espree: "npm:^10.4.0" + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/types": "npm:^8.52.0" + eslint-visitor-keys: "npm:^5.0.0" + espree: "npm:^11.0.0" estraverse: "npm:^5.3.0" picomatch: "npm:^4.0.3" peerDependencies: eslint: ">=9.0.0" - checksum: 10c0/dfd33107209dac554a6b88d40813bfadd938e901ee7b853dfff9b66c9ffe0601790591865ad67e05f1f1061fc3c958087ad25efc1117375fe2487bfa69c44352 + checksum: 10c0/257872abe3841466bf7c5c434d75915c44c77e6707bdca05cec463f56ec87c538489d55b0b7026ac3480baacf5d58536539a29d6c70d116af3a86cc42ee97232 languageName: node linkType: hard @@ -4279,30 +4138,30 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.5.0": - version: 0.5.17 - resolution: "@swc/helpers@npm:0.5.17" + version: 0.5.18 + resolution: "@swc/helpers@npm:0.5.18" dependencies: tslib: "npm:^2.8.0" - checksum: 10c0/fe1f33ebb968558c5a0c595e54f2e479e4609bff844f9ca9a2d1ffd8dd8504c26f862a11b031f48f75c95b0381c2966c3dd156e25942f90089badd24341e7dbb + checksum: 10c0/cb32d72e32f775c30287bffbcf61c89ea3a963608cb3a4a675a3f9af545b8b3ab0bc9930432a5520a7307daaa87538158e253584ae1cf39f3e7e6e83408a2d51 languageName: node linkType: hard "@tanstack/react-virtual@npm:^3.13.9": - version: 3.13.12 - resolution: "@tanstack/react-virtual@npm:3.13.12" + version: 3.13.18 + resolution: "@tanstack/react-virtual@npm:3.13.18" dependencies: - "@tanstack/virtual-core": "npm:3.13.12" + "@tanstack/virtual-core": "npm:3.13.18" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/0eda3d5691ec3bf93a1cdaa955f4972c7aa9a5026179622824bb52ff8c47e59ee4634208e52d77f43ffb3ce435ee39a0899d6a81f6316918ce89d68122490371 + checksum: 10c0/600fabdf9fe077d609dad5114025c502aa1125d21bfd64ae4eeeab4743de1c76ca0d5c29ca4cba0c1220834a9dfedbe0c7304276b0e0e7f9048e2c8a5562fa26 languageName: node linkType: hard -"@tanstack/virtual-core@npm:3.13.12": - version: 3.13.12 - resolution: "@tanstack/virtual-core@npm:3.13.12" - checksum: 10c0/483f38761b73db05c181c10181f0781c1051be3350ae5c378e65057e5f1fdd6606e06e17dbaad8a5e36c04b208ea1a1344cacd4eca0dcde60f335cf398e4d698 +"@tanstack/virtual-core@npm:3.13.18": + version: 3.13.18 + resolution: "@tanstack/virtual-core@npm:3.13.18" + checksum: 10c0/f43a92bbe4e891242a6bb5274ee60b166534abfcd5115cbe7f92f833e745941ac3b8cb808dd0537cbf1e0c5004aacd2d2cab4a1ce738f7930ec723566e37ec11 languageName: node linkType: hard @@ -4666,11 +4525,11 @@ __metadata: linkType: hard "@types/d3-shape@npm:*, @types/d3-shape@npm:^3.1.0": - version: 3.1.7 - resolution: "@types/d3-shape@npm:3.1.7" + version: 3.1.8 + resolution: "@types/d3-shape@npm:3.1.8" dependencies: "@types/d3-path": "npm:*" - checksum: 10c0/38e59771c1c4c83b67aa1f941ce350410522a149d2175832fdc06396b2bb3b2c1a2dd549e0f8230f9f24296ee5641a515eaf10f55ee1ef6c4f83749e2dd7dcfd + checksum: 10c0/49ec2172b9eb66fc1d036e2a23966216bb972e9af51ddbed134df24bd71aedf207bb1ef81903a119eb4e1f5e927cf44beacaf64c9af86474e5548594b102b574 languageName: node linkType: hard @@ -4892,20 +4751,20 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=13.7.0": - version: 24.10.1 - resolution: "@types/node@npm:24.10.1" + version: 25.0.9 + resolution: "@types/node@npm:25.0.9" dependencies: undici-types: "npm:~7.16.0" - checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002 + checksum: 10c0/a757efafe303d9c8833eb53c2e9a0981cd5ac725cdc04c5612a71db86468c938778d4fa328be4231b68fffc68258638764df7b9c69e86cf55f0bb67105eb056f languageName: node linkType: hard -"@types/node@npm:22.19.3": - version: 22.19.3 - resolution: "@types/node@npm:22.19.3" +"@types/node@npm:22.19.7": + version: 22.19.7 + resolution: "@types/node@npm:22.19.7" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/a30a75d503da795ddbd5f8851014f3e91925c2a63fa3f14128d54c998f25d682dfba96dc9589c73cf478b87a16d88beb790b11697bb8cd5bee913079237a58f2 + checksum: 10c0/0a4b13fd51306a72393f145693063e074be1bf884bdc642c879436d2c053ac9d573cc0a0d69867f60b14d3f1b9d03317e8ec0e4377f7527cb645e7b2a5d34045 languageName: node linkType: hard @@ -5031,19 +4890,19 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:16 || 17 || 18 || 19, @types/react@npm:19.2.7": - version: 19.2.7 - resolution: "@types/react@npm:19.2.7" +"@types/react@npm:*, @types/react@npm:16 || 17 || 18 || 19, @types/react@npm:19.2.8": + version: 19.2.8 + resolution: "@types/react@npm:19.2.8" dependencies: csstype: "npm:^3.2.2" - checksum: 10c0/a7b75f1f9fcb34badd6f84098be5e35a0aeca614bc91f93d2698664c0b2ba5ad128422bd470ada598238cebe4f9e604a752aead7dc6f5a92261d0c7f9b27cfd1 + checksum: 10c0/832834998c4ee971fca72ecf1eb95dc924ad3931a2112c687a4dae498aabd115c5fa4db09186853e34a646226b0223808c8f867df03d17601168f9cf119448de languageName: node linkType: hard -"@types/relay-runtime@npm:*, @types/relay-runtime@npm:20.1.0": - version: 20.1.0 - resolution: "@types/relay-runtime@npm:20.1.0" - checksum: 10c0/34bd18da234094fc982201d437ecd8b826a25e08d418ca78186df1b47a21ef4349fac56d3b78c82bd6c3e2c4add4dc89b1b80766dd4b5511e7d97639bd4b295b +"@types/relay-runtime@npm:*, @types/relay-runtime@npm:20.1.1": + version: 20.1.1 + resolution: "@types/relay-runtime@npm:20.1.1" + checksum: 10c0/37bb94d5bf2acb6d524bb7ba8e644ff81dba452e37385b2a037752c3ac024adaebe7f69e92130a2ece092b21526650ec4657f5d37307af89b5a1b43e11768b0d languageName: node linkType: hard @@ -5102,145 +4961,138 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.50.0" +"@typescript-eslint/eslint-plugin@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.53.0" dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/type-utils": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" - ignore: "npm:^7.0.0" + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.53.0" + "@typescript-eslint/type-utils": "npm:8.53.0" + "@typescript-eslint/utils": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.4.0" peerDependencies: - "@typescript-eslint/parser": ^8.50.0 + "@typescript-eslint/parser": ^8.53.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/032038ee029d1e0984e7c189c3e8173dc4fb909c3ab4d272227e62e6d1872eb9853699c72d46e269c0a084f113ea01fa00d4b61620190276b224fa1b5a5cbd80 + checksum: 10c0/c28925423023853591696f20844c9365ad4353c8beb004fc5ccc1995903c42202070165a2c750f53abf43420ff8daa19d874010efc4ba925311ca2f320ce55fe languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/parser@npm:8.50.0" +"@typescript-eslint/parser@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/parser@npm:8.53.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" - debug: "npm:^4.3.4" + "@typescript-eslint/scope-manager": "npm:8.53.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + debug: "npm:^4.4.3" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/3bdc9e7b2190285abf7350039056b104725fa70cbd769695717f9948669de4987db7103a7011d33d25d44e9474fe02404746816b8eba72642e17815cb6b0b2e6 + checksum: 10c0/5c6aae71f4015fc3ebbfe6fa6e040dcc99fc15b6bd36631b67ae61523b2da57651cbb1cd2de430380df5fd13cd03c43f233073af6a8a85714e651a3db74a5cf6 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/project-service@npm:8.50.0" +"@typescript-eslint/project-service@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/project-service@npm:8.53.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.50.0" - "@typescript-eslint/types": "npm:^8.50.0" - debug: "npm:^4.3.4" + "@typescript-eslint/tsconfig-utils": "npm:^8.53.0" + "@typescript-eslint/types": "npm:^8.53.0" + debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/54fdf4c8540eb8e592ab4818345935300bf5776621274cdc8bb942e72e84a4d2566b047b77218f6c851de26eab759c45153a39557ed2c2d1054d180d587d9780 + checksum: 10c0/b01302890cf853e9bb1d2b19e402ec0ede1388fec833528847d32d65d0e3e03867a14632f816f4f3058e40707b001fab208bf2950ff1e8d7cbbc6c1d57b969d4 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/scope-manager@npm:8.50.0" +"@typescript-eslint/scope-manager@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/scope-manager@npm:8.53.0" dependencies: - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" - checksum: 10c0/62a374aaa0bf7d185be43a4d7dd420d7135ab8f13f5cb4e602e16fdf712f0e00e6ab3fc8a31321e19922d27b867579b0b08c4040b23d528853f4b73e9ebcff3b + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + checksum: 10c0/338a7471aaa793858a23498b6ad37da8f419a8ee05cc4105d569b2c676e0f2d7a45806b88a8c8d3454f438f329b61df8e73ae582863a20eb0996529f9275e3c2 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.50.0, @typescript-eslint/tsconfig-utils@npm:^8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.0" +"@typescript-eslint/tsconfig-utils@npm:8.53.0, @typescript-eslint/tsconfig-utils@npm:^8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.53.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/5398d26e4a7bec866cc783f5f329a4fed1bc07cd4d21c5c32929a7524b1ebf8ae8e15ca7a035d1177630d86b614ecd3243d63289228bbe292526dbcbf9fae430 + checksum: 10c0/1a136519d4e0c4ae9471f55468ad0a52175b8b41da28188bd7e4efcf72c2c8528aeb3a1b1c9d27f2d94ab0c8d9a91e08ebc1fed5fc8628c9808112427f306428 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/type-utils@npm:8.50.0" +"@typescript-eslint/type-utils@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/type-utils@npm:8.53.0" dependencies: - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + "@typescript-eslint/utils": "npm:8.53.0" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.4.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/7ebd9a1ebd0cbb6eca9864439f80c2432545bd3ac38dee706be0004c78a26a9908003aa4f0825c0745f4fa1356ffacc0848dd230eae22a6516a02710ab645157 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.50.0, @typescript-eslint/types@npm:^8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/types@npm:8.50.0" - checksum: 10c0/15ec0d75deb331c5ccda726ad95d7f2801fde0f5edfe70425bbdede9e3c9e93b18e7890c9bc42f86ebd65221ebce75e6cc536a65cb1fbbdb0763df22ac392c7a + checksum: 10c0/6d7d6eb80a6b214d9c8e185527e21fa44e1f4d2fe48d4f29f964f8c3921da47757a8cc537edc5c233fc47af02a487935c176b1c918ce4d22ba8341dbd1b455e0 languageName: node linkType: hard -"@typescript-eslint/types@npm:^8.47.0": - version: 8.48.1 - resolution: "@typescript-eslint/types@npm:8.48.1" - checksum: 10c0/366b8140f4c69319f1796b66b33c0c6e16eb6cbe543b9517003104e12ed143b620c1433ccf60d781a629d9433bd509a363c0c9d21fd438c17bb8840733af6caa +"@typescript-eslint/types@npm:8.53.0, @typescript-eslint/types@npm:^8.52.0, @typescript-eslint/types@npm:^8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/types@npm:8.53.0" + checksum: 10c0/a88681795becbe857f9868427c0d75c2ab2fb1acde14907b8791709b6d7835400bf9a0b41f22e97a13f1274e0082f5675692b815e30268e6eada492913100306 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.50.0" +"@typescript-eslint/typescript-estree@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.53.0" dependencies: - "@typescript-eslint/project-service": "npm:8.50.0" - "@typescript-eslint/tsconfig-utils": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" - debug: "npm:^4.3.4" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" + "@typescript-eslint/project-service": "npm:8.53.0" + "@typescript-eslint/tsconfig-utils": "npm:8.53.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + debug: "npm:^4.4.3" + minimatch: "npm:^9.0.5" + semver: "npm:^7.7.3" tinyglobby: "npm:^0.2.15" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.4.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/30344ba5aab687dc50d805c33d4b481cc68c96acdcc679e8a1f46c5b4d8ba1ee562e3f377a4dc1c6418adf5b3fd342b31e5d30e54d0e7b18628ef6b1fb484341 + checksum: 10c0/31819fba9fbef3e3ab494409b18ff40042cc3e7a4ba72fe06475062b7e196aaf9752e526a1c51abf3002627833b387279f00fdfa66886b05c028e129a57b550a languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/utils@npm:8.50.0" +"@typescript-eslint/utils@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/utils@npm:8.53.0" dependencies: - "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.53.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/4069fbf56717401629c86ea1e36df3a7dc1bbbf5c11ec7b26add2b61cdb9070b48786dc45c8e35a872a0cddced1edef654557e27420b9a666616cead539b3ec0 + checksum: 10c0/6af761fc5ed89606bd8dd1abf7c526afe0060c115035a4fcddfa173ba8a01b7422edf84bc4d74aab2a086911db57a893a2753b3c025ace3e86adc1c2259a6253 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.50.0" +"@typescript-eslint/visitor-keys@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.53.0" dependencies: - "@typescript-eslint/types": "npm:8.50.0" + "@typescript-eslint/types": "npm:8.53.0" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/a13337ecc2042229b922b03882d6691df63053445aa8860f6fcc1da59d04d05f75d4e0ee132551b76d5c5f665e881eb89f327a6f0e83240860f913dff5d745ee + checksum: 10c0/be2062073c9fd28762f73d442e8536f16e1eab0935df463ed45bd95575b4b79b4a4ca1f45c04b1964dc424b8d25c6489253e3ea2236bb74cff9b7e02e1e7f5be languageName: node linkType: hard @@ -5402,50 +5254,49 @@ __metadata: languageName: node linkType: hard -"@vitest/coverage-v8@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/coverage-v8@npm:4.0.16" +"@vitest/coverage-v8@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/coverage-v8@npm:4.0.17" dependencies: "@bcoe/v8-coverage": "npm:^1.0.2" - "@vitest/utils": "npm:4.0.16" - ast-v8-to-istanbul: "npm:^0.3.8" + "@vitest/utils": "npm:4.0.17" + ast-v8-to-istanbul: "npm:^0.3.10" istanbul-lib-coverage: "npm:^3.2.2" istanbul-lib-report: "npm:^3.0.1" - istanbul-lib-source-maps: "npm:^5.0.6" istanbul-reports: "npm:^3.2.0" magicast: "npm:^0.5.1" obug: "npm:^2.1.1" std-env: "npm:^3.10.0" tinyrainbow: "npm:^3.0.3" peerDependencies: - "@vitest/browser": 4.0.16 - vitest: 4.0.16 + "@vitest/browser": 4.0.17 + vitest: 4.0.17 peerDependenciesMeta: "@vitest/browser": optional: true - checksum: 10c0/3edd18dc994949d5180a3fbd9c1af4ca4756735e82cffb73b3c0918ad23a4c71521287a205cc61a39b63453448e9bfd207f82b2d472fd757dfbb47987dbe99a8 + checksum: 10c0/a7120ec1446f4a7a3d0d1c2963a406ec281e99a2c7d163725de0a7ad166d9b996f00a14f8386da09bc3fde0441356e4fb9f3ff99e3cb5a3e8d09ddc8c076cf42 languageName: node linkType: hard -"@vitest/expect@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/expect@npm:4.0.16" +"@vitest/expect@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/expect@npm:4.0.17" dependencies: "@standard-schema/spec": "npm:^1.0.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.16" - "@vitest/utils": "npm:4.0.16" + "@vitest/spy": "npm:4.0.17" + "@vitest/utils": "npm:4.0.17" chai: "npm:^6.2.1" tinyrainbow: "npm:^3.0.3" - checksum: 10c0/add4dde3548b6f65b6d7d364607713f9db258642add248a23805fa1172e48d76a7822080249efb882120b408772684569b2e78581ed3d5f282e215d7f21be183 + checksum: 10c0/cdaa6827aa3a9473d51fd0944bcd698a94507929fa3c98b00bbdb74342319ec04279f01108d7d2dd7cbcd0d8062f65a3f21bb3615c0d5223e61adcc036c8b370 languageName: node linkType: hard -"@vitest/mocker@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/mocker@npm:4.0.16" +"@vitest/mocker@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/mocker@npm:4.0.17" dependencies: - "@vitest/spy": "npm:4.0.16" + "@vitest/spy": "npm:4.0.17" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.21" peerDependencies: @@ -5456,54 +5307,54 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/cf4469a4745e3cdd46e8ee4e20aa04369e7f985d40175d974d3a6f6d331bf9d8610f9638c5a18f7ff59a30ff04b19f4b823457b4c79142186fe463fa4cee80c5 + checksum: 10c0/54e657fa5b79764926b15aac993528bfe7083f6731209253617b1f27d328aa3297fcbf96b67e84d1a5632553231f795585f2396f563837cf117a574c87f5cef7 languageName: node linkType: hard -"@vitest/pretty-format@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/pretty-format@npm:4.0.16" +"@vitest/pretty-format@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/pretty-format@npm:4.0.17" dependencies: tinyrainbow: "npm:^3.0.3" - checksum: 10c0/11243e9c2d2d011ae23825c6b7464a4385a4a4efc4ceb28b7854bb9d73491f440b89d12f62c5c9737d26375cf9585b11bc20183d4dea4e983e79d5e162407eb9 + checksum: 10c0/10a2dd7e2daf7ee006107d380bbd28b66b09a7014d31087daab0dea7dee0d12868cfcf6b3372729268502fd9065162345b68b9b9c5d225f5c6c2fd2c664a2a86 languageName: node linkType: hard -"@vitest/runner@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/runner@npm:4.0.16" +"@vitest/runner@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/runner@npm:4.0.17" dependencies: - "@vitest/utils": "npm:4.0.16" + "@vitest/utils": "npm:4.0.17" pathe: "npm:^2.0.3" - checksum: 10c0/7f4614a9fe5e9f3683d30fb82d1489796c669df45fbc0beb22d39539e4b12ebef462062705545ca04391a0406af62088cbf1d613a812ecc9ea753a0edbfd5d26 + checksum: 10c0/f4ccc236d1ed5ba2186d5f36ff0306d4ac7b711a40d7316ad6fd71c0f7229482b19969a8737e87670f3d4efb08f2138ff5b47a744fd7ae8db6c03cf991293a04 languageName: node linkType: hard -"@vitest/snapshot@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/snapshot@npm:4.0.16" +"@vitest/snapshot@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/snapshot@npm:4.0.17" dependencies: - "@vitest/pretty-format": "npm:4.0.16" + "@vitest/pretty-format": "npm:4.0.17" magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/4fa63ffa4f30c909078210a1edcb059dbfa3ec3deaebb8f93637f65a7efae9a2d7714129bae0cf615512a683e925cf31f281fc4cb02f1fdc4c72f68ce21ca11f + checksum: 10c0/31a047a097b13eff6c0f5393ea3e7203771ae9a22afe6465cd9023fd2ed516ddccd84523d48504a032c9d04a86a12e3f1235e08bb2ffc7d7a125e372c41ef53d languageName: node linkType: hard -"@vitest/spy@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/spy@npm:4.0.16" - checksum: 10c0/2502918e703d60ef64854d0fa83ebf94da64b80e81b80c319568feee3d86069fd46e24880a768edba06c8caba13801e44005e17a0f16d9b389486f24d539f0bf +"@vitest/spy@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/spy@npm:4.0.17" + checksum: 10c0/c290731ba3392f11eaba8fc7fa08063a3a4d14af6baeec210b260ccd5a46613196fb4a8ff3ac8bf91a9606aef90eee9b6364bda130ce71abff368e35dfe2b265 languageName: node linkType: hard -"@vitest/utils@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/utils@npm:4.0.16" +"@vitest/utils@npm:4.0.17": + version: 4.0.17 + resolution: "@vitest/utils@npm:4.0.17" dependencies: - "@vitest/pretty-format": "npm:4.0.16" + "@vitest/pretty-format": "npm:4.0.17" tinyrainbow: "npm:^3.0.3" - checksum: 10c0/bba35b4e102be03e106ced227809437573aa5c5f64d512301ca8de127dcb91cbedc11a2e823305f8ba82528c909c10510ec8c7e3d92b3d6d1c1aec33e143572a + checksum: 10c0/1e2e4d7d7709ec022f603a1e12015523a2290f326c0bbe0c6bd5481ec396d4efc6bf8c738d601915d88e74267e9841df1e05157edced10f5048865204aeb86ff languageName: node linkType: hard @@ -6089,14 +5940,14 @@ __metadata: languageName: node linkType: hard -"ast-v8-to-istanbul@npm:^0.3.8": - version: 0.3.8 - resolution: "ast-v8-to-istanbul@npm:0.3.8" +"ast-v8-to-istanbul@npm:^0.3.10": + version: 0.3.10 + resolution: "ast-v8-to-istanbul@npm:0.3.10" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.31" estree-walker: "npm:^3.0.3" js-tokens: "npm:^9.0.1" - checksum: 10c0/6f7d74fc36011699af6d4ad88ecd8efc7d74bd90b8e8dbb1c69d43c8f4bec0ed361fb62a5b5bd98bbee02ee87c62cd8bcc25a39634964e45476bf5489dfa327f + checksum: 10c0/8a7a07c04f8f130b8a5abb76cdb31cce06a8eb4b7d4abbe207bc721132127ae332e857b96aa415ac43ec2c6c9312508210c598f61a7de2d0e3db5615e6b03183 languageName: node linkType: hard @@ -6214,11 +6065,11 @@ __metadata: linkType: hard "baseline-browser-mapping@npm:^2.9.0": - version: 2.9.2 - resolution: "baseline-browser-mapping@npm:2.9.2" + version: 2.9.15 + resolution: "baseline-browser-mapping@npm:2.9.15" bin: baseline-browser-mapping: dist/cli.js - checksum: 10c0/4f9be09e20261ed26f19e9b95454dcb8d8371b87983c57cd9f70b9572e9b3053577f0d8d6d91297bdb605337747680686e22f62522a6e57ae2488fcacf641188 + checksum: 10c0/e5c8cb8600fcbed8132f122b737b00b5b3fcf25a119ea5e42476e6d6b2263274ddc5df16d4cffebbcd46974b691008558973b06100508903ea8a382a5edd34ab languageName: node linkType: hard @@ -6260,8 +6111,8 @@ __metadata: linkType: hard "body-parser@npm:^2.2.1": - version: 2.2.1 - resolution: "body-parser@npm:2.2.1" + version: 2.2.2 + resolution: "body-parser@npm:2.2.2" dependencies: bytes: "npm:^3.1.2" content-type: "npm:^1.0.5" @@ -6269,10 +6120,10 @@ __metadata: http-errors: "npm:^2.0.0" iconv-lite: "npm:^0.7.0" on-finished: "npm:^2.4.1" - qs: "npm:^6.14.0" + qs: "npm:^6.14.1" raw-body: "npm:^3.0.1" type-is: "npm:^2.0.1" - checksum: 10c0/ce9608cff4114a908c09e8f57c7b358cd6fef66100320d01244d4c141448d7a6710c4051cc9d6191f8c6b3c7fa0f5b040c7aa1b6bbeb5462e27e668e64cb15bd + checksum: 10c0/95a830a003b38654b75166ca765358aa92ee3d561bf0e41d6ccdde0e1a0c9783cab6b90b20eb635d23172c010b59d3563a137a738e74da4ba714463510d05137 languageName: node linkType: hard @@ -6450,9 +6301,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001759": - version: 1.0.30001759 - resolution: "caniuse-lite@npm:1.0.30001759" - checksum: 10c0/b0f415960ba34995cda18e0d25c4e602f6917b9179290a76bdd0311423505b78cc93e558a90c98a22a1cc6b1781ab720ef6beea24ec7e29a1c1164ca72eac3a2 + version: 1.0.30001764 + resolution: "caniuse-lite@npm:1.0.30001764" + checksum: 10c0/3fbc2bcb35792bd860e20210283e7c700aab10c5af435dbb8bfbf952edccaa3e7de8b479af0f600c4d23f269dbc166e16b7b72df5cd1981653b252174c9cbfa8 languageName: node linkType: hard @@ -6473,9 +6324,9 @@ __metadata: linkType: hard "chai@npm:^6.2.1": - version: 6.2.1 - resolution: "chai@npm:6.2.1" - checksum: 10c0/0c2d84392d7c6d44ca5d14d94204f1760e22af68b83d1f4278b5c4d301dabfc0242da70954dd86b1eda01e438f42950de6cf9d569df2103678538e4014abe50b + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 languageName: node linkType: hard @@ -7022,13 +6873,14 @@ __metadata: linkType: hard "cssstyle@npm:^5.3.4": - version: 5.3.4 - resolution: "cssstyle@npm:5.3.4" + version: 5.3.7 + resolution: "cssstyle@npm:5.3.7" dependencies: - "@asamuzakjp/css-color": "npm:^4.1.0" - "@csstools/css-syntax-patches-for-csstree": "npm:1.0.14" + "@asamuzakjp/css-color": "npm:^4.1.1" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.21" css-tree: "npm:^3.1.0" - checksum: 10c0/7499ea8cbc2f759ded275428e0811d147baa6a964a44577711cee5edabee2230cf76b6bd20a556603f99ebc6fff80afdcba6c00bcbb1d41ae50cd09cd9fe9a2d + lru-cache: "npm:^11.2.4" + checksum: 10c0/9330f014f4209df06305264b92b8e963dfef636fdc2ae7d13f24ea7da6468aba1dc5eb13082621258bdd22cbd7fb7cb291894e188a3cdf660e8b79cd2c5e5e0e languageName: node linkType: hard @@ -7139,9 +6991,9 @@ __metadata: linkType: hard "d3-format@npm:1 - 3": - version: 3.1.0 - resolution: "d3-format@npm:3.1.0" - checksum: 10c0/049f5c0871ebce9859fc5e2f07f336b3c5bfff52a2540e0bac7e703fce567cd9346f4ad1079dd18d6f1e0eaa0599941c1810898926f10ac21a31fd0a34b4aa75 + version: 3.1.2 + resolution: "d3-format@npm:3.1.2" + checksum: 10c0/0de452ae07585238e7f01607a7e0066665c34609652188b6ac7dc9f424f69465a425e07d16d79bd0e5955202ac7f241c66d0c76f68a79fc6f4857c94cf420652 languageName: node linkType: hard @@ -7450,7 +7302,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -7788,9 +7640,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.263": - version: 1.5.265 - resolution: "electron-to-chromium@npm:1.5.265" - checksum: 10c0/f0409d15b02b61df5004d2bf6db0a2a01f42273fb46944c243d13139c83e8a07ad556656fd5f09758ae9b612ebf9862535afc6bffd47395890d5df7551274a4b + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10c0/0732bdb891b657f2e43266a3db8cf86fff6cecdcc8d693a92beff214e136cb5c2ee7dc5945ed75fa1db16e16bad0c38695527a020d15f39e79084e0b2e447621 languageName: node linkType: hard @@ -7887,9 +7739,9 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": - version: 1.24.0 - resolution: "es-abstract@npm:1.24.0" +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0, es-abstract@npm:^1.24.1": + version: 1.24.1 + resolution: "es-abstract@npm:1.24.1" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" @@ -7945,7 +7797,7 @@ __metadata: typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" which-typed-array: "npm:^1.1.19" - checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 + checksum: 10c0/fca062ef8b5daacf743732167d319a212d45cb655b0bb540821d38d715416ae15b04b84fc86da9e2c89135aa7b337337b6c867f84dcde698d75d55688d5d765c languageName: node linkType: hard @@ -7964,26 +7816,26 @@ __metadata: linkType: hard "es-iterator-helpers@npm:^1.2.1": - version: 1.2.1 - resolution: "es-iterator-helpers@npm:1.2.1" + version: 1.2.2 + resolution: "es-iterator-helpers@npm:1.2.2" dependencies: call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.6" + es-abstract: "npm:^1.24.1" es-errors: "npm:^1.3.0" - es-set-tostringtag: "npm:^2.0.3" + es-set-tostringtag: "npm:^2.1.0" function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.6" + get-intrinsic: "npm:^1.3.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" has-property-descriptors: "npm:^1.0.2" has-proto: "npm:^1.2.0" has-symbols: "npm:^1.1.0" internal-slot: "npm:^1.1.0" - iterator.prototype: "npm:^1.1.4" + iterator.prototype: "npm:^1.1.5" safe-array-concat: "npm:^1.1.3" - checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f + checksum: 10c0/1ced8abf845a45e660dd77b5f3a64358421df70e4a0bd1897d5ddfefffed8409a6a2ca21241b9367e639df9eca74abc1c678b3020bffe6bee1f1826393658ddb languageName: node linkType: hard @@ -8010,7 +7862,7 @@ __metadata: languageName: node linkType: hard -"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0": +"es-set-tostringtag@npm:^2.1.0": version: 2.1.0 resolution: "es-set-tostringtag@npm:2.1.0" dependencies: @@ -8043,14 +7895,14 @@ __metadata: linkType: hard "es-toolkit@npm:^1.39.3": - version: 1.42.0 - resolution: "es-toolkit@npm:1.42.0" + version: 1.44.0 + resolution: "es-toolkit@npm:1.44.0" dependenciesMeta: "@trivago/prettier-plugin-sort-imports@4.3.0": unplugged: true prettier-plugin-sort-re-exports@0.0.1: unplugged: true - checksum: 10c0/ee577b23336296116be423a5d01a6af827c80a10971507ae26cdb146b60ce0a930bf7bdb719ac0dc4f962ec74542a2423b934e24eb47efe1bc911862b12da109 + checksum: 10c0/b80ff52ddc85ba26914cda57c9d4e46379ccc38c60dc097ef0d065cc0b20f95a16cf8d537969eea600b51c6687b5900a6cce67489db16d5ccc14d47597a29c34 languageName: node linkType: hard @@ -8143,95 +7995,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.25.0": - version: 0.25.12 - resolution: "esbuild@npm:0.25.12" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.12" - "@esbuild/android-arm": "npm:0.25.12" - "@esbuild/android-arm64": "npm:0.25.12" - "@esbuild/android-x64": "npm:0.25.12" - "@esbuild/darwin-arm64": "npm:0.25.12" - "@esbuild/darwin-x64": "npm:0.25.12" - "@esbuild/freebsd-arm64": "npm:0.25.12" - "@esbuild/freebsd-x64": "npm:0.25.12" - "@esbuild/linux-arm": "npm:0.25.12" - "@esbuild/linux-arm64": "npm:0.25.12" - "@esbuild/linux-ia32": "npm:0.25.12" - "@esbuild/linux-loong64": "npm:0.25.12" - "@esbuild/linux-mips64el": "npm:0.25.12" - "@esbuild/linux-ppc64": "npm:0.25.12" - "@esbuild/linux-riscv64": "npm:0.25.12" - "@esbuild/linux-s390x": "npm:0.25.12" - "@esbuild/linux-x64": "npm:0.25.12" - "@esbuild/netbsd-arm64": "npm:0.25.12" - "@esbuild/netbsd-x64": "npm:0.25.12" - "@esbuild/openbsd-arm64": "npm:0.25.12" - "@esbuild/openbsd-x64": "npm:0.25.12" - "@esbuild/openharmony-arm64": "npm:0.25.12" - "@esbuild/sunos-x64": "npm:0.25.12" - "@esbuild/win32-arm64": "npm:0.25.12" - "@esbuild/win32-ia32": "npm:0.25.12" - "@esbuild/win32-x64": "npm:0.25.12" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b - languageName: node - linkType: hard - "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -8434,6 +8197,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^5.0.0": + version: 5.0.0 + resolution: "eslint-visitor-keys@npm:5.0.0" + checksum: 10c0/5ec68b7ae350f6e7813a9ab469f8c64e01e5a954e6e6ee6dc441cc24d315eb342e5fb81ab5fc21f352cf0125096ab4ed93ca892f602a1576ad1eedce591fe64a + languageName: node + linkType: hard + "eslint@npm:9.39.2": version: 9.39.2 resolution: "eslint@npm:9.39.2" @@ -8494,6 +8264,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^11.0.0": + version: 11.0.0 + resolution: "espree@npm:11.0.0" + dependencies: + acorn: "npm:^8.15.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/1e07fdb2a135bb9996a4b23baad51980dde7bcdf4d7115cdec06437663790f4bbe3416eb560fc7dc7330c01a6006f789722f1e5b243208f4cb6e054efef57afd + languageName: node + linkType: hard + "esprima@npm:^4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -8505,11 +8286,11 @@ __metadata: linkType: hard "esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" + version: 1.7.0 + resolution: "esquery@npm:1.7.0" dependencies: estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 languageName: node linkType: hard @@ -8588,9 +8369,9 @@ __metadata: linkType: hard "expect-type@npm:^1.2.2": - version: 1.2.2 - resolution: "expect-type@npm:1.2.2" - checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd languageName: node linkType: hard @@ -8978,11 +8759,11 @@ __metadata: linkType: hard "framer-motion@npm:^12": - version: 12.23.25 - resolution: "framer-motion@npm:12.23.25" + version: 12.26.2 + resolution: "framer-motion@npm:12.26.2" dependencies: - motion-dom: "npm:^12.23.23" - motion-utils: "npm:^12.23.6" + motion-dom: "npm:^12.26.2" + motion-utils: "npm:^12.24.10" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -8995,7 +8776,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10c0/cfcc2da1f3ffe50abe6f657e08b61912acbafffc0a0652d2518c1928e1e23a01a580a2e7feeb9234d7c8028e573a9896519b38fa0f570390b2e4624bafdf4063 + checksum: 10c0/f2950155c982a95c423d6349cf26b2add58ef7e9633dc1629d2e8506b453699cddaf4c8ad9d8f220c53ce4f6af77740422d4f1c27f2033aeadfee5b941e1b59d languageName: node linkType: hard @@ -9294,10 +9075,10 @@ __metadata: languageName: node linkType: hard -"globals@npm:16.5.0": - version: 16.5.0 - resolution: "globals@npm:16.5.0" - checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 +"globals@npm:17.0.0": + version: 17.0.0 + resolution: "globals@npm:17.0.0" + checksum: 10c0/e3c169fdcb0fc6755707b697afb367bea483eb29992cfc0de1637382eb893146e17f8f96db6d7453e3696b478a7863ae2000e6c71cd2f4061410285106d3847a languageName: node linkType: hard @@ -9597,12 +9378,12 @@ __metadata: languageName: node linkType: hard -"html-encoding-sniffer@npm:^4.0.0": - version: 4.0.0 - resolution: "html-encoding-sniffer@npm:4.0.0" +"html-encoding-sniffer@npm:^6.0.0": + version: 6.0.0 + resolution: "html-encoding-sniffer@npm:6.0.0" dependencies: - whatwg-encoding: "npm:^3.1.1" - checksum: 10c0/523398055dc61ac9b34718a719cb4aa691e4166f29187e211e1607de63dc25ac7af52ca7c9aead0c4b3c0415ffecb17326396e1202e2e86ff4bca4c0ee4c6140 + "@exodus/bytes": "npm:^1.6.0" + checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025 languageName: node linkType: hard @@ -9620,9 +9401,9 @@ __metadata: languageName: node linkType: hard -"html-react-parser@npm:5.2.10": - version: 5.2.10 - resolution: "html-react-parser@npm:5.2.10" +"html-react-parser@npm:5.2.11": + version: 5.2.11 + resolution: "html-react-parser@npm:5.2.11" dependencies: domhandler: "npm:5.0.3" html-dom-parser: "npm:5.1.2" @@ -9634,7 +9415,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/af93dc554eb47633a2a8d1a5c9276768730606fd30f7355de30e02ad6ef16bb269b0e4b4ae7190ba1a056da43acf2c17cd4fae0f3981486d117f6b3646245974 + checksum: 10c0/9173df6db4523aab142e1e4c4b55bf9ae6a18a6581d98a2ce9d92af0d3a5dccebb39f26719cf3b5d35d7cc21d49fc774508dda54146771ac7e67e9a1fa252d8a languageName: node linkType: hard @@ -9695,7 +9476,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^2.0.0, http-errors@npm:~2.0.1": +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" dependencies: @@ -9812,7 +9593,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -9821,12 +9602,12 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.7.0, iconv-lite@npm:~0.7.0": - version: 0.7.0 - resolution: "iconv-lite@npm:0.7.0" +"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.1, iconv-lite@npm:~0.7.0": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" dependencies: safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f + checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 languageName: node linkType: hard @@ -9844,7 +9625,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.0": +"ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d @@ -9859,9 +9640,9 @@ __metadata: linkType: hard "immer@npm:^11.0.0": - version: 11.0.1 - resolution: "immer@npm:11.0.1" - checksum: 10c0/d505c1aa6d5d7ff03196e0a508af2853839eb1a3d5af51d55ca5815c46981b74765faf357fbd276b151109b07dd6b564077be92b0247b0a4effa78130166030d + version: 11.1.3 + resolution: "immer@npm:11.1.3" + checksum: 10c0/2d0bcb7946274bb99254b8129026da9579591569afdd4f29e5ef3ebf231375c7ab97c344ffbfc9eeabea27a2145623fca1287c17e3ebd007c8cdcfd82954eeb7 languageName: node linkType: hard @@ -10415,17 +10196,6 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^5.0.6": - version: 5.0.6 - resolution: "istanbul-lib-source-maps@npm:5.0.6" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.23" - debug: "npm:^4.1.1" - istanbul-lib-coverage: "npm:^3.0.0" - checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f - languageName: node - linkType: hard - "istanbul-reports@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" @@ -10436,7 +10206,7 @@ __metadata: languageName: node linkType: hard -"iterator.prototype@npm:^1.1.4": +"iterator.prototype@npm:^1.1.5": version: 1.1.5 resolution: "iterator.prototype@npm:1.1.5" dependencies: @@ -10548,16 +10318,17 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:27.3.0": - version: 27.3.0 - resolution: "jsdom@npm:27.3.0" +"jsdom@npm:27.4.0": + version: 27.4.0 + resolution: "jsdom@npm:27.4.0" dependencies: "@acemir/cssom": "npm:^0.9.28" "@asamuzakjp/dom-selector": "npm:^6.7.6" + "@exodus/bytes": "npm:^1.6.0" cssstyle: "npm:^5.3.4" data-urls: "npm:^6.0.0" decimal.js: "npm:^10.6.0" - html-encoding-sniffer: "npm:^4.0.0" + html-encoding-sniffer: "npm:^6.0.0" http-proxy-agent: "npm:^7.0.2" https-proxy-agent: "npm:^7.0.6" is-potential-custom-element-name: "npm:^1.0.1" @@ -10567,7 +10338,6 @@ __metadata: tough-cookie: "npm:^6.0.0" w3c-xmlserializer: "npm:^5.0.0" webidl-conversions: "npm:^8.0.0" - whatwg-encoding: "npm:^3.1.1" whatwg-mimetype: "npm:^4.0.0" whatwg-url: "npm:^15.1.0" ws: "npm:^8.18.3" @@ -10577,7 +10347,7 @@ __metadata: peerDependenciesMeta: canvas: optional: true - checksum: 10c0/b022ed8f6ce175afd97fbd42eb65b03b2be3b23df86cf87f018b6d2e757682fe8348e719a14780d6fa3fe8a65e531ba71b38db80f312818a32b77f01e31f267e + checksum: 10c0/291bb71a611dbaed81ce516587b71a5ffd9d43337d65bbd0731e7924cd7018f5871cf66614facadfd0dffec2b23a0fc57b2ee36b5a39e20f0f569e2949b3418c languageName: node linkType: hard @@ -10956,7 +10726,14 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:4, lodash-es@npm:4.17.21, lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": +"lodash-es@npm:4, lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": + version: 4.17.22 + resolution: "lodash-es@npm:4.17.22" + checksum: 10c0/5f28a262183cca43e08c580622557f393cb889386df2d8adf7c852bfdff7a84c5e629df5aa6c5c6274e83b38172f239d3e4e72e1ad27352d9ae9766627338089 + languageName: node + linkType: hard + +"lodash-es@npm:4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 @@ -11040,7 +10817,7 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4": +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.4": version: 11.2.4 resolution: "lru-cache@npm:11.2.4" checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 @@ -11861,7 +11638,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1, mime-types@npm:^3.0.2": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -11895,7 +11672,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -12059,19 +11836,19 @@ __metadata: languageName: node linkType: hard -"motion-dom@npm:^12.23.23": - version: 12.23.23 - resolution: "motion-dom@npm:12.23.23" +"motion-dom@npm:^12.26.2": + version: 12.26.2 + resolution: "motion-dom@npm:12.26.2" dependencies: - motion-utils: "npm:^12.23.6" - checksum: 10c0/139705731085063519b88f23fcc5b1c13e15707a4ff3365da02ef9a4bf2a2d8ebed9a151c57e7f215ccd9e822365d93c16e28e619fbf25611f61dcff5ee81d75 + motion-utils: "npm:^12.24.10" + checksum: 10c0/9ec544749008b5f53eb3e3140ac720d8a2e0c1f41178ecb29f5fb7629d92c4b92657bd8449a051baa852090a2ea9eacd0fa3c4c7e64bbaea3504c35fc610d430 languageName: node linkType: hard -"motion-utils@npm:^12.23.6": - version: 12.23.6 - resolution: "motion-utils@npm:12.23.6" - checksum: 10c0/c058e8ba6423b3baa63e985bcc669877ee7d9579d938f5348b4e60c5ea1b4b33dd7f4877434436a4a5807f3cf00370d3fd4079a6fdd6309c5c87aa62b311a897 +"motion-utils@npm:^12.24.10": + version: 12.24.10 + resolution: "motion-utils@npm:12.24.10" + checksum: 10c0/b73e2e414480b0b162a5cbc9195856d113b5e7c23a055b1613143176694fdfabe87a6a41c3c2f205549e368ca541b609455344bd774d7d836f09a551562042e2 languageName: node linkType: hard @@ -12243,9 +12020,9 @@ __metadata: linkType: hard "nodemailer@npm:^7.0.6": - version: 7.0.11 - resolution: "nodemailer@npm:7.0.11" - checksum: 10c0/208f108fdb4c5dd0e3a2f013578d53dad505cf1b9c7a084f6d22fc9d6f3912daafb4a23793ca568ff848afc35f15f4eb24382d3f6f9fb8ede4a8410d4ca63618 + version: 7.0.12 + resolution: "nodemailer@npm:7.0.12" + checksum: 10c0/b03948744423386b5fd7cb5bdf60797f2f71c4d3db202eedf7e0037429b5887cb90b641e7ca244af3b96e0b76949e3135cba96336fb0d3ade00ae33727ca8cf8 languageName: node linkType: hard @@ -12443,8 +12220,8 @@ __metadata: "@cfworker/json-schema": "npm:4.1.1" "@ckeditor/ckeditor5-react": "npm:9.5.0" "@eslint/js": "npm:9.39.2" - "@faker-js/faker": "npm:10.1.0" - "@filigran/chatbot": "npm:1.0.2" + "@faker-js/faker": "npm:10.2.0" + "@filigran/chatbot": "npm:1.1.0" "@fontsource/geologica": "npm:5.2.8" "@fontsource/ibm-plex-sans": "npm:5.2.8" "@graphiql/toolkit": "npm:0.11.3" @@ -12461,27 +12238,27 @@ __metadata: "@rjsf/core": "npm:5.24.13" "@rjsf/mui": "npm:5.24.13" "@rjsf/utils": "npm:5.24.13" - "@stylistic/eslint-plugin": "npm:5.6.1" + "@stylistic/eslint-plugin": "npm:5.7.0" "@testing-library/dom": "npm:10.4.1" "@testing-library/jest-dom": "npm:6.9.1" "@testing-library/react": "npm:16.3.1" "@testing-library/user-event": "npm:14.6.1" "@types/d3-scale": "npm:4.0.9" "@types/html-to-pdfmake": "npm:2.4.4" - "@types/node": "npm:22.19.3" + "@types/node": "npm:22.19.7" "@types/qrcode": "npm:1.5.6" "@types/ramda": "npm:0.31.1" - "@types/react": "npm:19.2.7" + "@types/react": "npm:19.2.8" "@types/react-csv": "npm:1.1.10" "@types/react-dom": "npm:19.2.3" "@types/react-grid-layout": "npm:1.3.6" "@types/react-relay": "npm:18.2.1" "@types/react-syntax-highlighter": "npm:15.5.13" "@types/react-transition-group": "npm:4.4.12" - "@types/relay-runtime": "npm:20.1.0" + "@types/relay-runtime": "npm:20.1.1" "@types/relay-test-utils": "npm:19.0.0" "@vitejs/plugin-react": "npm:5.1.2" - "@vitest/coverage-v8": "npm:4.0.16" + "@vitest/coverage-v8": "npm:4.0.17" analytics: "npm:0.8.19" apexcharts: "npm:4.4.0" axios: "npm:1.13.2" @@ -12510,11 +12287,11 @@ __metadata: formik: "npm:2.4.9" formik-mui: "npm:5.0.0-alpha.0" formik-mui-lab: "npm:1.0.0" - globals: "npm:16.5.0" + globals: "npm:17.0.0" graphiql: "npm:4.1.2" graphql: "npm:16.12.0" graphql-ws: "npm:5.16.2" - html-react-parser: "npm:5.2.10" + html-react-parser: "npm:5.2.11" html-to-image: "npm:1.11.13" html-to-pdfmake: "npm:2.5.32" http-proxy-middleware: "npm:3.0.5" @@ -12523,7 +12300,7 @@ __metadata: is-svg: "npm:6.1.0" js-base64: "npm:3.7.8" js-file-download: "npm:0.4.12" - jsdom: "npm:27.3.0" + jsdom: "npm:27.4.0" json5: "npm:2.2.3" leaflet: "npm:1.9.4" license-checker-rseidelsohn: "npm:4.4.2" @@ -12533,7 +12310,7 @@ __metadata: moment: "npm:2.30.1" moment-timezone: "npm:0.6.0" monocart-reporter: "npm:2.9.23" - pdfmake: "npm:0.2.20" + pdfmake: "npm:0.2.21" prop-types: "npm:15.8.1" qrcode: "npm:1.5.4" ramda: "npm:0.32.0" @@ -12546,7 +12323,7 @@ __metadata: react-dom: "npm:19.2.3" react-draggable: "npm:4.5.0" react-force-graph-2d: "npm:1.29.0" - react-force-graph-3d: "npm:1.24.4" + react-force-graph-3d: "npm:1.29.0" react-grid-layout: "npm:1.5.3" react-intl: "npm:7.1.14" react-leaflet: "npm:5.0.0" @@ -12554,11 +12331,11 @@ __metadata: react-material-ui-carousel: "npm:3.4.2" react-mde: "npm:11.5.0" react-otp-input: "npm:3.1.1" - react-pdf: "npm:10.2.0" + react-pdf: "npm:10.3.0" react-rectangle-selection: "npm:1.0.4" react-relay: "npm:20.1.1" react-relay-network-modern: "npm:6.2.2" - react-router-dom: "npm:6.30.2" + react-router-dom: "npm:6.30.3" react-syntax-highlighter: "npm:16.1.0" react-transition-group: "npm:4.4.5" react-virtualized: "npm:9.22.6" @@ -12574,13 +12351,13 @@ __metadata: rxjs: "npm:7.8.2" three-spritetext: "npm:1.10.0" typescript: "npm:5.9.3" - typescript-eslint: "npm:8.50.0" + typescript-eslint: "npm:8.53.0" use-analytics: "npm:1.1.0" - uuid: "npm:11.1.0" - vite: "npm:7.3.0" + uuid: "npm:13.0.0" + vite: "npm:7.3.1" vite-plugin-relay: "npm:2.1.0" vite-plugin-static-copy: "npm:3.1.4" - vitest: "npm:4.0.16" + vitest: "npm:4.0.17" yup: "npm:1.7.1" dependenciesMeta: canvas: @@ -12828,15 +12605,15 @@ __metadata: languageName: node linkType: hard -"pdfmake@npm:0.2.20": - version: 0.2.20 - resolution: "pdfmake@npm:0.2.20" +"pdfmake@npm:0.2.21": + version: 0.2.21 + resolution: "pdfmake@npm:0.2.21" dependencies: "@foliojs-fork/linebreak": "npm:^1.1.2" "@foliojs-fork/pdfkit": "npm:^0.15.3" - iconv-lite: "npm:^0.6.3" - xmldoc: "npm:^2.0.1" - checksum: 10c0/6464d408aa7c214561753f01701ffa387e8ba2784e6000977fc1b7e360958d2a256989451f72a73bf9d4d313a0b77d0d11378bf8451a65edfa95c56c82eac043 + iconv-lite: "npm:^0.7.1" + xmldoc: "npm:^2.0.3" + checksum: 10c0/72385405832823f72adc82f7956c34d8ad6a8821ae0c48145346508c924a64979008fb88ef3d2686fc67ca18da93e0ac2823873f3e4d7f8f0364d9d487a5d5e2 languageName: node linkType: hard @@ -12939,9 +12716,9 @@ __metadata: linkType: hard "preact@npm:10": - version: 10.28.0 - resolution: "preact@npm:10.28.0" - checksum: 10c0/039c5077ccce61d6a781d5f63df24c5c7205270abe52464708852b8e8d7fc926cdd07fc4a116af282e5a1e41b62e8a4065a5999e0e2db521faad6a1c033a8b1c + version: 10.28.2 + resolution: "preact@npm:10.28.2" + checksum: 10c0/eb60bf526eb6971701e6ac9c25236aca451f17f99e9c24704419196989b15bb576ed3101e084b151cd0fb30546b3e5e1ba73b774e8be2f2ed8187db42ec65faf languageName: node linkType: hard @@ -13094,12 +12871,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.14.0": - version: 6.14.0 - resolution: "qs@npm:6.14.0" +"qs@npm:^6.14.0, qs@npm:^6.14.1": + version: 6.14.1 + resolution: "qs@npm:6.14.1" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c + checksum: 10c0/0e3b22dc451f48ce5940cbbc7c7d9068d895074f8c969c0801ac15c1313d1859c4d738e46dc4da2f498f41a9ffd8c201bd9fb12df67799b827db94cc373d2613 languageName: node linkType: hard @@ -13227,7 +13004,7 @@ __metadata: languageName: node linkType: hard -"react-draggable@npm:4.5.0, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.6": +"react-draggable@npm:4.5.0, react-draggable@npm:^4.4.6, react-draggable@npm:^4.5.0": version: 4.5.0 resolution: "react-draggable@npm:4.5.0" dependencies: @@ -13260,16 +13037,16 @@ __metadata: languageName: node linkType: hard -"react-force-graph-3d@npm:1.24.4": - version: 1.24.4 - resolution: "react-force-graph-3d@npm:1.24.4" +"react-force-graph-3d@npm:1.29.0": + version: 1.29.0 + resolution: "react-force-graph-3d@npm:1.29.0" dependencies: - 3d-force-graph: "npm:^1.73" + 3d-force-graph: "npm:^1.79" prop-types: "npm:15" - react-kapsule: "npm:2" + react-kapsule: "npm:^2.5" peerDependencies: react: "*" - checksum: 10c0/acd174a7c2904e75e38eea73b2bf83f4cfe46d774af66916e0c6e1f5fa8289f5014046553a12add98bdc8c01493d9bb09aba8ce44d416559610db228f7f70d25 + checksum: 10c0/c6ba6828486da968325b78be50a6da3cea8c69ff34f647a1917e85014fc1b6233de5e5e966079bce514532452a7848ee5f6a99d4883313774b29d042be8c3a3b languageName: node linkType: hard @@ -13333,14 +13110,14 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^19.0.0, react-is@npm:^19.2.0": - version: 19.2.1 - resolution: "react-is@npm:19.2.1" - checksum: 10c0/0ebeaedb4ff615055cbcd758c7e22ba9644e21110adbd293dd1aada3591abf7399152a786cd120e324c10706d75e28c2130c27d1b9b5ae637aef4c52f4d17a91 +"react-is@npm:^19.0.0, react-is@npm:^19.2.3": + version: 19.2.3 + resolution: "react-is@npm:19.2.3" + checksum: 10c0/2b54c422c21b8dbd68a435a1cce21ecd5b6f06f48659531f7d53dd7368365da5a67e946f352fb2010d11ca40658aa67bec90995f0f1ec5556c0f71dbffe54994 languageName: node linkType: hard -"react-kapsule@npm:2, react-kapsule@npm:^2.5": +"react-kapsule@npm:^2.5": version: 2.5.7 resolution: "react-kapsule@npm:2.5.7" dependencies: @@ -13435,9 +13212,9 @@ __metadata: languageName: node linkType: hard -"react-pdf@npm:10.2.0": - version: 10.2.0 - resolution: "react-pdf@npm:10.2.0" +"react-pdf@npm:10.3.0": + version: 10.3.0 + resolution: "react-pdf@npm:10.3.0" dependencies: clsx: "npm:^2.0.0" dequal: "npm:^2.0.3" @@ -13454,7 +13231,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/a9138b9c54c41899efc98165b99149bc8089aff98f82030d7810764940066ffde584929f0899c10bd527949c19afdce01a10e10a0e49d8c8131c877361132198 + checksum: 10c0/433d55ce4bc1b93655ab0edbac6f08b27d544939e76756ed7c34f26cf8b0f39e0f6ef965a771da4511a98ba9b126a6e7cecb18c616e4ab54eb2ee0b1cfba5bfa languageName: node linkType: hard @@ -13565,38 +13342,39 @@ __metadata: linkType: hard "react-resizable@npm:^3.0.5": - version: 3.0.5 - resolution: "react-resizable@npm:3.0.5" + version: 3.1.3 + resolution: "react-resizable@npm:3.1.3" dependencies: prop-types: "npm:15.x" - react-draggable: "npm:^4.0.3" + react-draggable: "npm:^4.5.0" peerDependencies: react: ">= 16.3" - checksum: 10c0/cfe50aa6efb79e0aa09bd681a5beab2fcd1186737c4952eb4c3974ed9395d5d263ccd1130961d06b8f5e24c8f544dd2967b5c740ce68719962d1771de7bdb350 + react-dom: ">= 16.3" + checksum: 10c0/de0f4447369381a446c0ce5f5770e85ec3a522a540d8eaf11998ec2d0b37835f879eb58f1765a89269ead53018c1eabf10b293eae5cb41c41a89057f2741563d languageName: node linkType: hard -"react-router-dom@npm:6.30.2": - version: 6.30.2 - resolution: "react-router-dom@npm:6.30.2" +"react-router-dom@npm:6.30.3": + version: 6.30.3 + resolution: "react-router-dom@npm:6.30.3" dependencies: - "@remix-run/router": "npm:1.23.1" - react-router: "npm:6.30.2" + "@remix-run/router": "npm:1.23.2" + react-router: "npm:6.30.3" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10c0/d0c6edf4e2aa7639b4a4f64a7747f03d8861bdf4857e8981b1cff1451b7cb91fcdcd7e315a6e3df910271b2f5071825d2aec218d5f7890f2269fc074f198e42a + checksum: 10c0/e8a1e13c662ed6ee71a785bd3418ba04b700bcd8aff6ea7b32524371e8abb1c85568cd4fe9b9e9d555b8101fd415f6f1796531f593da60be179ce75b37038657 languageName: node linkType: hard -"react-router@npm:6.30.2": - version: 6.30.2 - resolution: "react-router@npm:6.30.2" +"react-router@npm:6.30.3": + version: 6.30.3 + resolution: "react-router@npm:6.30.3" dependencies: - "@remix-run/router": "npm:1.23.1" + "@remix-run/router": "npm:1.23.2" peerDependencies: react: ">=16.8" - checksum: 10c0/cff5ea92d248d2230adc46d4e2ed3fbeddfaf1ae2e63411da8b7ea6ddc207d71dbc522c05c492e671e553e2153934f4ab180ac02bd36205b274e097f2cfe6fc4 + checksum: 10c0/a0a74bf5a933cf0abd47e0eac1d3a505cd66b866e3ee8f20d8016885d3b4361ba3ba72dee026248c6125e631b191ba6ad109184c892281cea6cb747c71bf5940 languageName: node linkType: hard @@ -14116,31 +13894,34 @@ __metadata: linkType: hard "rollup@npm:^4.43.0": - version: 4.53.3 - resolution: "rollup@npm:4.53.3" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.53.3" - "@rollup/rollup-android-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-x64": "npm:4.53.3" - "@rollup/rollup-freebsd-arm64": "npm:4.53.3" - "@rollup/rollup-freebsd-x64": "npm:4.53.3" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-arm64-musl": "npm:4.53.3" - "@rollup/rollup-linux-loong64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-musl": "npm:4.53.3" - "@rollup/rollup-linux-s390x-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-musl": "npm:4.53.3" - "@rollup/rollup-openharmony-arm64": "npm:4.53.3" - "@rollup/rollup-win32-arm64-msvc": "npm:4.53.3" - "@rollup/rollup-win32-ia32-msvc": "npm:4.53.3" - "@rollup/rollup-win32-x64-gnu": "npm:4.53.3" - "@rollup/rollup-win32-x64-msvc": "npm:4.53.3" + version: 4.55.1 + resolution: "rollup@npm:4.55.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.55.1" + "@rollup/rollup-android-arm64": "npm:4.55.1" + "@rollup/rollup-darwin-arm64": "npm:4.55.1" + "@rollup/rollup-darwin-x64": "npm:4.55.1" + "@rollup/rollup-freebsd-arm64": "npm:4.55.1" + "@rollup/rollup-freebsd-x64": "npm:4.55.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.55.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.55.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.55.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.55.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.55.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.55.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.55.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.55.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.55.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.55.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.55.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.55.1" + "@rollup/rollup-linux-x64-musl": "npm:4.55.1" + "@rollup/rollup-openbsd-x64": "npm:4.55.1" + "@rollup/rollup-openharmony-arm64": "npm:4.55.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.55.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.55.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.55.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.55.1" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -14166,8 +13947,12 @@ __metadata: optional: true "@rollup/rollup-linux-loong64-gnu": optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true "@rollup/rollup-linux-ppc64-gnu": optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true "@rollup/rollup-linux-riscv64-musl": @@ -14178,6 +13963,8 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true + "@rollup/rollup-openbsd-x64": + optional: true "@rollup/rollup-openharmony-arm64": optional: true "@rollup/rollup-win32-arm64-msvc": @@ -14192,7 +13979,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/a21305aac72013083bd0dec92162b0f7f24cacf57c876ca601ec76e892895952c9ea592c1c07f23b8c125f7979c2b17f7fb565e386d03ee4c1f0952ac4ab0d75 + checksum: 10c0/267309f0db5c5493b2b163643dceed6e57aa20fcd75d40cf44740b8b572e747a0f9e1694b11ff518583596c37fe13ada09bf676956f50073c16cdac09e633a66 languageName: node linkType: hard @@ -14266,10 +14053,10 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.2.4, sax@npm:^1.4.1": - version: 1.4.3 - resolution: "sax@npm:1.4.3" - checksum: 10c0/45bba07561d93f184a8686e1a543418ced8c844b994fbe45cc49d5cd2fc8ac7ec949dae38565e35e388ad0cca2b75997a29b6857c927bf6553da3f80ed0e4e62 +"sax@npm:^1.4.1, sax@npm:^1.4.3": + version: 1.4.4 + resolution: "sax@npm:1.4.4" + checksum: 10c0/acb642f2de02ad6ae157cbf91fb026acea80cdf92e88c0aec2aa350c7db3479f62a7365c34a58e3b70a72ce11fa856a02c38cfd27f49e83c18c9c7e1d52aee55 languageName: node linkType: hard @@ -14308,7 +14095,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5 || 6 || 7, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.7.1": +"semver@npm:2 || 3 || 4 || 5 || 6 || 7, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.1, semver@npm:^7.7.3": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -14327,21 +14114,21 @@ __metadata: linkType: hard "send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.0 - resolution: "send@npm:1.2.0" + version: 1.2.1 + resolution: "send@npm:1.2.1" dependencies: - debug: "npm:^4.3.5" + debug: "npm:^4.4.3" encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" etag: "npm:^1.8.1" fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - mime-types: "npm:^3.0.1" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" ms: "npm:^2.1.3" on-finished: "npm:^2.4.1" range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 + statuses: "npm:^2.0.2" + checksum: 10c0/fbbbbdc902a913d65605274be23f3d604065cfc3ee3d78bf9fc8af1dc9fc82667c50d3d657f5e601ac657bac9b396b50ee97bd29cd55436320cf1cddebdcec72 languageName: node linkType: hard @@ -14371,14 +14158,14 @@ __metadata: linkType: hard "serve-static@npm:^2.2.0": - version: 2.2.0 - resolution: "serve-static@npm:2.2.0" + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" dependencies: encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" parseurl: "npm:^1.3.3" send: "npm:^1.2.0" - checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 + checksum: 10c0/37986096e8572e2dfaad35a3925fa8da0c0969f8814fd7788e84d4d388bc068cf0c06d1658509788e55bed942a6b6d040a8a267fa92bb9ffb1179f8bacde5fd7 languageName: node linkType: hard @@ -14723,7 +14510,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:^2.0.1, statuses@npm:~2.0.2": +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f @@ -15009,9 +14796,9 @@ __metadata: linkType: hard "tabbable@npm:^6.0.0": - version: 6.3.0 - resolution: "tabbable@npm:6.3.0" - checksum: 10c0/57ba019d29b5cfa0c862248883bcec0e6d29d8f156ba52a1f425e7cfeca4a0fc701ab8d035c4c86ddf74ecdbd0e9f454a88d9b55d924a51f444038e9cd14d7a0 + version: 6.4.0 + resolution: "tabbable@npm:6.4.0" + checksum: 10c0/d931427f4a96b801fd8801ba296a702119e06f70ad262fed8abc5271225c9f1ca51b89fdec4fb2f22e1d35acb3d2881db0a17cedc758272e9ecb540d00299d76 languageName: node linkType: hard @@ -15070,8 +14857,8 @@ __metadata: linkType: hard "terser@npm:^5.31.1": - version: 5.44.1 - resolution: "terser@npm:5.44.1" + version: 5.46.0 + resolution: "terser@npm:5.46.0" dependencies: "@jridgewell/source-map": "npm:^0.3.3" acorn: "npm:^8.15.0" @@ -15079,7 +14866,7 @@ __metadata: source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10c0/ee7a76692cb39b1ed22c30ff366c33ff3c977d9bb769575338ff5664676168fcba59192fb5168ef80c7cd901ef5411a1b0351261f5eaa50decf0fc71f63bde75 + checksum: 10c0/93ad468f13187c4f66b609bbfc00a6aee752007779ca3157f2c1ee063697815748d6010fd449a16c30be33213748431d5f54cc0224ba6a3fbbf5acd3582a4356 languageName: node linkType: hard @@ -15127,7 +14914,7 @@ __metadata: languageName: node linkType: hard -"three@npm:>=0.118 <1": +"three@npm:0.181.2": version: 0.181.2 resolution: "three@npm:0.181.2" checksum: 10c0/b34b6240fbedebc7f8a9317c062f7ee5339de0e56250ba2ae3de52edb9c517dc8e9aaf6fe242e1bfff56808081e095db28be5ef1a05947e0c82aefada95c628b @@ -15303,12 +15090,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" +"ts-api-utils@npm:^2.4.0": + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f + checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083 languageName: node linkType: hard @@ -15450,18 +15237,18 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.50.0": - version: 8.50.0 - resolution: "typescript-eslint@npm:8.50.0" +"typescript-eslint@npm:8.53.0": + version: 8.53.0 + resolution: "typescript-eslint@npm:8.53.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.50.0" - "@typescript-eslint/parser": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" + "@typescript-eslint/eslint-plugin": "npm:8.53.0" + "@typescript-eslint/parser": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + "@typescript-eslint/utils": "npm:8.53.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/63f96505fdfc7d0ff0b5d0338c5877a76ef0933ea3a0c90b2a5d73a7f0ee18d778dc673d9345de3bcb6f37ae02fd930301ef13b2e162c4850f08ad89f1c19613 + checksum: 10c0/eb12a31f6bfb1edd9a381ca85c1095b917f57d56ab58720e893f48637613761d384300debe43f19999724201627596cc5b5dcde97f92de32b7146b038072fd7c languageName: node linkType: hard @@ -15768,8 +15555,8 @@ __metadata: linkType: hard "update-browserslist-db@npm:^1.2.0": - version: 1.2.2 - resolution: "update-browserslist-db@npm:1.2.2" + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -15777,7 +15564,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/39c3ea08b397ffc8dc3a1c517f5c6ed5cc4179b5e185383dab9bf745879623c12062a2e6bf4f9427cc59389c7bfa0010e86858b923c1e349e32fdddd9b043bb2 + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec languageName: node linkType: hard @@ -15849,15 +15636,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:11.1.0": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" - bin: - uuid: dist/esm/bin/uuid - checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 - languageName: node - linkType: hard - "uuid@npm:13.0.0": version: 13.0.0 resolution: "uuid@npm:13.0.0" @@ -15999,9 +15777,9 @@ __metadata: languageName: node linkType: hard -"vite@npm:7.3.0": - version: 7.3.0 - resolution: "vite@npm:7.3.0" +"vite@npm:7.3.1, vite@npm:^6.0.0 || ^7.0.0": + version: 7.3.1 + resolution: "vite@npm:7.3.1" dependencies: esbuild: "npm:^0.27.0" fdir: "npm:^6.5.0" @@ -16050,76 +15828,21 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/0457c196cdd5761ec351c0f353945430fbad330e615b9eeab729c8ae163334f18acdc1d9cd7d9d673dbf111f07f6e4f0b25d4ac32360e65b4a6df9991046f3ff + checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0": - version: 7.2.6 - resolution: "vite@npm:7.2.6" +"vitest@npm:4.0.17": + version: 4.0.17 + resolution: "vitest@npm:4.0.17" dependencies: - esbuild: "npm:^0.25.0" - fdir: "npm:^6.5.0" - fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.43.0" - tinyglobby: "npm:^0.2.15" - peerDependencies: - "@types/node": ^20.19.0 || >=22.12.0 - jiti: ">=1.21.0" - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: ">=0.54.8" - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - bin: - vite: bin/vite.js - checksum: 10c0/d444a159ab8f0f854d596d1938f201b449d59ed4d336e587be9dc89005467214d85848c212c2495f76a8421372ffe4d061d023d659600f1aaa3ba5ac13e804f7 - languageName: node - linkType: hard - -"vitest@npm:4.0.16": - version: 4.0.16 - resolution: "vitest@npm:4.0.16" - dependencies: - "@vitest/expect": "npm:4.0.16" - "@vitest/mocker": "npm:4.0.16" - "@vitest/pretty-format": "npm:4.0.16" - "@vitest/runner": "npm:4.0.16" - "@vitest/snapshot": "npm:4.0.16" - "@vitest/spy": "npm:4.0.16" - "@vitest/utils": "npm:4.0.16" + "@vitest/expect": "npm:4.0.17" + "@vitest/mocker": "npm:4.0.17" + "@vitest/pretty-format": "npm:4.0.17" + "@vitest/runner": "npm:4.0.17" + "@vitest/snapshot": "npm:4.0.17" + "@vitest/spy": "npm:4.0.17" + "@vitest/utils": "npm:4.0.17" es-module-lexer: "npm:^1.7.0" expect-type: "npm:^1.2.2" magic-string: "npm:^0.30.21" @@ -16137,10 +15860,10 @@ __metadata: "@edge-runtime/vm": "*" "@opentelemetry/api": ^1.9.0 "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.16 - "@vitest/browser-preview": 4.0.16 - "@vitest/browser-webdriverio": 4.0.16 - "@vitest/ui": 4.0.16 + "@vitest/browser-playwright": 4.0.17 + "@vitest/browser-preview": 4.0.17 + "@vitest/browser-webdriverio": 4.0.17 + "@vitest/ui": 4.0.17 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -16164,7 +15887,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/b195c272198f7957c11186eb70ee78e2ec0f4524b4b5306ca8f05e41b3d84c6a4a15d02fca58d82f2b32ba61f610ae8a2a23d463a8336d7323e4832db5eef223 + checksum: 10c0/e1648bbfe2d01e23ceb6856863344035d2a1c139f39e8b15859e6ea8dc510ac3ba425df7c45486492d85ca516472aa892540dfd11ab6ad0613be98fd56d40716 languageName: node linkType: hard @@ -16194,12 +15917,12 @@ __metadata: linkType: hard "watchpack@npm:^2.4.4": - version: 2.4.4 - resolution: "watchpack@npm:2.4.4" + version: 2.5.1 + resolution: "watchpack@npm:2.5.1" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c0/6c0901f75ce245d33991225af915eea1c5ae4ba087f3aee2b70dd377d4cacb34bef02a48daf109da9d59b2d31ec6463d924a0d72f8618ae1643dd07b95de5275 + checksum: 10c0/dffbb483d1f61be90dc570630a1eb308581e2227d507d783b1d94a57ac7b705ecd9a1a4b73d73c15eab596d39874e5276a3d9cb88bbb698bafc3f8d08c34cf17 languageName: node linkType: hard @@ -16218,9 +15941,9 @@ __metadata: linkType: hard "webidl-conversions@npm:^8.0.0": - version: 8.0.0 - resolution: "webidl-conversions@npm:8.0.0" - checksum: 10c0/3244e8a6534163bc3ee5f5f48b507b4bb74e34e7cc7c86a50cd02734753042b88343dae48321f34ad61ddc6b5c90cb1a5b2ee757b8be8e6fadc587a9f3db76cd + version: 8.0.1 + resolution: "webidl-conversions@npm:8.0.1" + checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46 languageName: node linkType: hard @@ -16269,15 +15992,6 @@ __metadata: languageName: node linkType: hard -"whatwg-encoding@npm:^3.1.1": - version: 3.1.1 - resolution: "whatwg-encoding@npm:3.1.1" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 10c0/273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e - languageName: node - linkType: hard - "whatwg-mimetype@npm:^4.0.0": version: 4.0.0 resolution: "whatwg-mimetype@npm:4.0.0" @@ -16359,8 +16073,8 @@ __metadata: linkType: hard "which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": - version: 1.1.19 - resolution: "which-typed-array@npm:1.1.19" + version: 1.1.20 + resolution: "which-typed-array@npm:1.1.20" dependencies: available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" @@ -16369,7 +16083,7 @@ __metadata: get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-tostringtag: "npm:^1.0.2" - checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f + checksum: 10c0/16fcdada95c8afb821cd1117f0ab50b4d8551677ac08187f21d4e444530913c9ffd2dac634f0c1183345f96344b69280f40f9a8bc52164ef409e555567c2604b languageName: node linkType: hard @@ -16466,8 +16180,8 @@ __metadata: linkType: hard "ws@npm:^8.18.3": - version: 8.18.3 - resolution: "ws@npm:8.18.3" + version: 8.19.0 + resolution: "ws@npm:8.19.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -16476,7 +16190,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + checksum: 10c0/4741d9b9bc3f9c791880882414f96e36b8b254e34d4b503279d6400d9a4b87a033834856dbdd94ee4b637944df17ea8afc4bce0ff4a1560d2166be8855da5b04 languageName: node linkType: hard @@ -16494,12 +16208,12 @@ __metadata: languageName: node linkType: hard -"xmldoc@npm:^2.0.1": - version: 2.0.2 - resolution: "xmldoc@npm:2.0.2" +"xmldoc@npm:^2.0.3": + version: 2.0.3 + resolution: "xmldoc@npm:2.0.3" dependencies: - sax: "npm:^1.2.4" - checksum: 10c0/dbf42e50a136e99be811238dd6b5bf7a0ff2af539b8aa0253f4932c9cc26cf8a20122e287fc0f12553d8cd2dc726a4ecac90cd1922654b788ac7bb1dc3e8cfa8 + sax: "npm:^1.4.3" + checksum: 10c0/8b01fc2b49f7dfa3fc0506d7ae4bab41476e15836114a1ab3794a5dc925498e2f4c248391479f57426e166a718082e6628c7112a89e6bf015ed780c9772f4406 languageName: node linkType: hard @@ -16673,8 +16387,8 @@ __metadata: linkType: hard "zustand@npm:^5": - version: 5.0.9 - resolution: "zustand@npm:5.0.9" + version: 5.0.10 + resolution: "zustand@npm:5.0.10" peerDependencies: "@types/react": ">=18.0.0" immer: ">=9.0.6" @@ -16689,7 +16403,7 @@ __metadata: optional: true use-sync-external-store: optional: true - checksum: 10c0/552849e4546c7760704d6509a5c412d57c62a1fa9e53169c939ba5e3d75f8cb3df50a64c3a22e6c3f1c8cc00de7543e4edd61ab5ae0c9169ba9a98e28303aba6 + checksum: 10c0/e6ddabf2b44f2c0b7362b0f549cb457d25516caa4c0465132037b62b6173552a43d48bb494c1707286f8d60b389fa249eb4242aa810f1c1d4b4d0b96df789cb8 languageName: node linkType: hard diff --git a/opencti-platform/opencti-graphql/.gitignore b/opencti-platform/opencti-graphql/.gitignore index 4c1235a957e2..569b3e2f0ada 100644 --- a/opencti-platform/opencti-graphql/.gitignore +++ b/opencti-platform/opencti-graphql/.gitignore @@ -5,16 +5,10 @@ build dist public config/ssl -config/demo.json -config/development.json -config/development.json.bak -config/production.json -config/reference.json -config/tanium.json -config/test.json -config/local.json -config/sync.json -config/prod-*.json +config/*.json +config/*.json.bak +!config/default.json +!config/test.json coverage test-results logs diff --git a/opencti-platform/opencti-graphql/config/schema/opencti.graphql b/opencti-platform/opencti-graphql/config/schema/opencti.graphql index c1d45436b5eb..fb93170c5375 100644 --- a/opencti-platform/opencti-graphql/config/schema/opencti.graphql +++ b/opencti-platform/opencti-graphql/config/schema/opencti.graphql @@ -816,6 +816,7 @@ type Synchronizer { ssl_verify: Boolean synchronized: Boolean queue_messages: Int! + toConfigurationExport: String! } type SynchronizerEdge { @@ -833,7 +834,9 @@ input SynchronizerAddInput { uri: String! @constraint(minLength: 2) token: String stream_id: String! @constraint(minLength: 2) - user_id: String + user_id: String! + automatic_user: Boolean + confidence_level: Int recover: DateTime current_state_date: DateTime listen_deletion: Boolean! @@ -842,6 +845,12 @@ input SynchronizerAddInput { synchronized: Boolean } + +input SynchronizerAddAutoUserInput { + user_name: String! + confidence_level: Int! +} + input SynchronizerFetchInput { uri: String! token: String @@ -1272,7 +1281,7 @@ interface BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] } @@ -1420,7 +1429,6 @@ type PublicSettings implements IntlSettings & ThemeSettings { platform_providers: [PublicProvider!]! platform_enterprise_edition_license_validated: Boolean! playground_enabled: Boolean! - metrics_definition: [MetricDefinition!] } @@ -1494,6 +1502,7 @@ type Settings implements InternalObject & BasicObject & ThemeSettings & IntlSett password_policy_min_lowercase: Int password_policy_min_uppercase: Int platform_messages: [SettingsMessage!] + platform_session_max_concurrent: Int messages_administration: [SettingsMessage!] @auth(for: [SETTINGS_SETPARAMETERS]) analytics_google_analytics_v4: String playground_enabled: Boolean! @@ -1554,7 +1563,7 @@ type Group implements InternalObject & BasicObject { standard_id: String! entity_type: String! auto_integration_assignation: [String]! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Group name: String! @@ -1736,7 +1745,7 @@ type User implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # User user_email: String! @@ -1930,7 +1939,7 @@ type Role implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Role name: String! @@ -1962,7 +1971,7 @@ type Capability implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Capability name: String! @@ -2123,6 +2132,16 @@ type RabbitMQConnection { pass: String! } +type S3Connection { + endpoint: String! + port: Int! + use_ssl: Boolean! + bucket_name: String! + bucket_region: String! + access_key: String! + secret_key: String! +} + input ConnectorInfoInput { run_and_terminate: Boolean! buffering: Boolean! @@ -2143,6 +2162,7 @@ type ConnectorInfo { type ConnectorConfig { connection: RabbitMQConnection! @auth(for: [CONNECTORAPI]) + s3: S3Connection! @auth(for: [CONNECTORAPI]) listen: String! listen_routing: String! listen_exchange: String! @@ -2202,7 +2222,7 @@ type Connector implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Connector name: String! @@ -2250,7 +2270,7 @@ type ManagedConnector implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Connector name: String! @@ -2280,7 +2300,7 @@ type ConnectorManager implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Manager name: String! @@ -2356,7 +2376,7 @@ interface StixObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2396,7 +2416,7 @@ interface StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject x_opencti_stix_ids: [StixId] @@ -2435,7 +2455,7 @@ type MarkingDefinition implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2508,7 +2528,7 @@ type Label implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2570,7 +2590,7 @@ type ExternalReference implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2627,6 +2647,9 @@ input ExternalReferenceAddInput { url: String hash: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] external_id: String created: DateTime modified: DateTime @@ -2658,7 +2681,7 @@ type KillChainPhase implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2749,7 +2772,7 @@ interface StixCoreObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2921,7 +2944,7 @@ interface StixDomainObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3093,7 +3116,7 @@ type AttackPattern implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3279,6 +3302,10 @@ input AttackPatternAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type AttackPatternForMatrix { attack_pattern_id: String! @@ -3331,7 +3358,7 @@ type Campaign implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3489,6 +3516,10 @@ input CampaignAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Containers @@ -3518,7 +3549,7 @@ interface Container { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3678,7 +3709,7 @@ type Note implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3867,6 +3898,10 @@ input NoteAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } input NoteUserAddInput { stix_id: String @@ -3916,7 +3951,7 @@ type ObservedData implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4102,6 +4137,10 @@ input ObservedDataAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ################ Opinions @@ -4130,7 +4169,7 @@ type Opinion implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4315,6 +4354,9 @@ input OpinionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] } input OpinionUserAddInput { stix_id: String @@ -4364,7 +4406,7 @@ type Report implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4561,7 +4603,11 @@ input ReportAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } ############## CoursesOfAction @@ -4588,7 +4634,7 @@ type CourseOfAction implements BasicObject & StixObject & StixCoreObject & StixD id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4743,6 +4789,10 @@ input CourseOfActionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Identities @@ -4773,7 +4823,7 @@ interface Identity { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4923,7 +4973,12 @@ input IdentityAddInput { clientMutationId: String created: DateTime modified: DateTime + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] update: Boolean + upsertOperations: [EditInput!] } ################ Individuals @@ -4949,7 +5004,7 @@ type Individual implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5113,6 +5168,10 @@ input IndividualAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ################ Sectors @@ -5139,7 +5198,7 @@ type Sector implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5299,6 +5358,10 @@ input SectorAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ################ Systems @@ -5324,7 +5387,7 @@ type System implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5487,6 +5550,10 @@ input SystemAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Infrastructures @@ -5518,7 +5585,7 @@ type Infrastructure implements BasicObject & StixObject & StixCoreObject & StixD id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5676,6 +5743,10 @@ input InfrastructureAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## IntrusionSets @@ -5704,7 +5775,7 @@ type IntrusionSet implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5869,6 +5940,10 @@ input IntrusionSetAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Locations @@ -5896,7 +5971,7 @@ interface Location { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6047,6 +6122,11 @@ input LocationAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ################ Positions @@ -6073,7 +6153,7 @@ type Position implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6234,6 +6314,10 @@ input PositionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ################ Cities @@ -6261,7 +6345,7 @@ type City implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6420,6 +6504,10 @@ input CityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } enum CountriesOrdering { @@ -6443,7 +6531,7 @@ type Country implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6600,6 +6688,10 @@ input CountryAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ################ Regions @@ -6624,7 +6716,7 @@ type Region implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6783,6 +6875,10 @@ input RegionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Malware @@ -6818,7 +6914,7 @@ type Malware implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6987,6 +7083,10 @@ input MalwareAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## ThreatActorsGroup @@ -7018,7 +7118,7 @@ interface ThreatActor implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7174,7 +7274,7 @@ type ThreatActorGroup implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7348,6 +7448,10 @@ input ThreatActorGroupAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Tools @@ -7373,7 +7477,7 @@ type Tool implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7529,6 +7633,10 @@ input ToolAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Vulnerabilities @@ -7558,7 +7666,7 @@ type Vulnerability implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7813,6 +7921,10 @@ input VulnerabilityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## Incident @@ -7846,7 +7958,7 @@ type Incident implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8011,6 +8123,10 @@ input IncidentAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ######## STIX CYBER OBSERVABLES ENTITIES @@ -8037,7 +8153,7 @@ interface StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8156,7 +8272,7 @@ type AutonomousSystem implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8282,12 +8398,16 @@ input AutonomousSystemAddInput { name: String rir: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Directory implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8417,12 +8537,16 @@ input DirectoryAddInput { mtime: DateTime atime: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8544,12 +8668,16 @@ type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyber input DomainNameAddInput { value: String! file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type EmailAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8673,12 +8801,16 @@ input EmailAddrAddInput { value: String display_name: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type EmailMessage implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8812,12 +8944,16 @@ input EmailMessageAddInput { received_lines: [String] body: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type EmailMimePartType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8943,11 +9079,16 @@ input EmailMimePartTypeAddInput { content_type: String content_disposition: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ############## HashedObservable input HashInput { algorithm: String! @constraint(minLength: 3) hash: String! @constraint(minLength: 5) + upsertOperations: [EditInput!] } type Hash { algorithm: String! @@ -8966,7 +9107,7 @@ interface HashedObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9087,7 +9228,7 @@ type Artifact implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9222,12 +9363,16 @@ input ArtifactAddInput { decryption_key: String x_opencti_additional_names: [String] file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type StixFile implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9371,12 +9516,16 @@ input StixFileAddInput { x_opencti_additional_names: [String] obsContent: ID file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type X509Certificate implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9555,12 +9704,16 @@ input X509CertificateAddInput { certificate_policies: String policy_mappings: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type IPv4Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9685,12 +9838,16 @@ input IPv4AddrAddInput { belongsTo: [String] resolvesTo: [String] file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9813,12 +9970,16 @@ type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb input IPv6AddrAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9940,12 +10101,16 @@ type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObs input MacAddrAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10067,12 +10232,16 @@ type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObser input MutexAddInput { name: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type NetworkTraffic implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10217,12 +10386,16 @@ input NetworkTrafficAddInput { src_packets: Int dst_packets: Int file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Process implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10390,12 +10563,16 @@ input ProcessAddInput { service_type: String # windows-service-type-enum service_status: String # windows-service-status-enum file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Software implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10538,12 +10715,16 @@ input SoftwareAddInput { version: String x_opencti_product: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10665,12 +10846,16 @@ type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObserva input UrlAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type UserAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10819,12 +11004,16 @@ input UserAccountAddInput { account_first_login: DateTime account_last_login: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type WindowsRegistryKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10949,13 +11138,17 @@ input WindowsRegistryKeyAddInput { attribute_key: String modified_time: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] number_of_subkeys: Int + upsertOperations: [EditInput!] } type WindowsRegistryValueType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11081,12 +11274,16 @@ input WindowsRegistryValueTypeAddInput { data: String data_type: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type CryptographicKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11208,12 +11405,16 @@ type CryptographicKey implements BasicObject & StixObject & StixCoreObject & Sti input CryptographicKeyAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11335,12 +11536,16 @@ type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & input CryptocurrencyWalletAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11462,12 +11667,16 @@ type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberOb input HostnameAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11589,12 +11798,16 @@ type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObserv input TextAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11716,12 +11929,16 @@ type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberO input UserAgentAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type BankAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11848,12 +12065,16 @@ input BankAccountAddInput { bic: String account_number: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11978,12 +12199,16 @@ type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixC input TrackingNumberAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Credential implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12108,12 +12333,16 @@ type Credential implements BasicObject & StixObject & StixCoreObject & StixCyber input CredentialAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12235,12 +12464,16 @@ type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCybe input PhoneNumberAddInput { value: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type PaymentCard implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12368,12 +12601,16 @@ input PaymentCardAddInput { cvv: Int holder_name: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type MediaContent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12504,12 +12741,16 @@ input MediaContentAddInput { url: String! publication_date: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12632,13 +12873,18 @@ type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObs input PersonaAddInput { persona_name: String! @constraint(minLength: 2, format: "not-blank") persona_type: String! + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type SSHKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12773,6 +13019,11 @@ input SSHKeyAddInput { comment: String created: DateTime expiration_date: DateTime + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } ###### RELATIONSHIPS @@ -12780,7 +13031,7 @@ interface BasicRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -12798,7 +13049,7 @@ type InternalRelationship implements BasicRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13149,7 +13400,7 @@ interface StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13226,7 +13477,7 @@ type StixCoreRelationship implements BasicRelationship & StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13334,6 +13585,7 @@ input StixCoreRelationshipAddInput { # Technical clientMutationId: String update: Boolean + upsertOperations: [EditInput!] } ############## StixSightingRelationships @@ -13370,7 +13622,7 @@ type StixSightingRelationship implements BasicRelationship & StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13444,6 +13696,7 @@ input StixSightingRelationshipAddInput { update: Boolean x_opencti_workflow_id: String x_opencti_modified_at: DateTime + upsertOperations: [EditInput!] } ############## StixRefRelationships @@ -13478,7 +13731,7 @@ type StixRefRelationship implements BasicRelationship & StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13541,6 +13794,9 @@ input StixRefRelationshipAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] } input StixRefRelationshipsAddInput { relationship_type: String! @@ -13548,6 +13804,17 @@ input StixRefRelationshipsAddInput { toIds: [StixRef!]! } +type SynchronizerAddInputFromImport { + name: String! + uri: String! + stream_id: String! + current_state_date: DateTime + listen_deletion: Boolean! + ssl_verify: Boolean! + no_dependencies: Boolean! + synchronized: Boolean! +} + ### QUERIES type Query { @@ -13789,7 +14056,9 @@ type Query { rules: [Rule] @auth(for: [KNOWLEDGE, SETTINGS_SETCUSTOMIZATION]) ruleManagerInfo: RuleManager @auth(for: [SETTINGS_SETCUSTOMIZATION]) synchronizer(id: String!): Synchronizer @auth(for: [INGESTION]) - synchronizers( + synchronizerAddInputFromImport(file: Upload!): SynchronizerAddInputFromImport! @auth(for: [CONNECTORAPI]) + + synchronizers( first: Int after: ID orderBy: SynchronizersOrdering @@ -15300,6 +15569,7 @@ type Mutation { ### SYNC synchronizerAdd(input: SynchronizerAddInput!): Synchronizer @auth(for: [CONNECTORAPI]) + synchronizerAddAutoUser(id: ID!, input: SynchronizerAddAutoUserInput!): Synchronizer @auth(for:[CONNECTORAPI]) synchronizerEdit(id: ID!): SynchronizerEditMutations @auth(for: [CONNECTORAPI]) synchronizerStart(id: ID!): Synchronizer @auth(for: [CONNECTORAPI]) synchronizerStop(id: ID!): Synchronizer @auth(for: [CONNECTORAPI]) @@ -15485,6 +15755,7 @@ type Mutation { externalReferences: [String] clientMutationId: String update: Boolean + upsertOperations: [EditInput!] AutonomousSystem: AutonomousSystemAddInput Directory: DirectoryAddInput DomainName: DomainNameAddInput @@ -15524,6 +15795,7 @@ type Mutation { stixCyberObservablesExportPush(entity_id: String, entity_type: String!, file: Upload!, file_markings: [String]!, listFilters: String): Boolean @auth(for: [CONNECTORAPI]) artifactImport( file: Upload! + fileMarkings: [String] x_opencti_description: String createdBy: String objectMarking: [String] diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 9553d9aa2ebd..dea5625a9540 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -1,6 +1,6 @@ { "name": "opencti-graphql", - "version": "6.9.3", + "version": "6.9.9", "private": true, "scripts": { "check-ts": "tsc --noEmit", @@ -16,9 +16,9 @@ "frontend:stack:analysis": "node builder/dev/stack.js && node --env-file=.env build/script-stack-analysis front", "lint": "DEBUG=eslint:cli-engine TIMING=1 eslint --quiet --cache --config eslint.config.mjs src", "build": "yarn install:python && yarn build:prod", - "start": "NODE_ENV=dev yarn build:dev && node --enable-source-maps build/back.js", - "start:e2e": "NODE_ENV=dev yarn build:dev && APP__ENABLED_DEV_FEATURES='[\"*\"]' EXPIRATION_SCHEDULER__ENABLED=false REDIS__NAMESPACE=e2e-start RABBITMQ__QUEUE_PREFIX=e2e MINIO__BUCKET_NAME=e2e-start-bucket ELASTICSEARCH__INDEX_PREFIX=e2e-start node build/back.js", - "start:light": "NODE_ENV=dev node build/back.js", + "start": "NODE_ENV=development yarn build:dev && node --enable-source-maps build/back.js", + "start:e2e": "NODE_ENV=development yarn build:dev && APP__ENABLED_DEV_FEATURES='[\"*\"]' EXPIRATION_SCHEDULER__ENABLED=false REDIS__NAMESPACE=e2e-start RABBITMQ__QUEUE_PREFIX=e2e MINIO__BUCKET_NAME=e2e-start-bucket ELASTICSEARCH__INDEX_PREFIX=e2e-start node build/back.js", + "start:light": "NODE_ENV=development node build/back.js", "start:cluster": "NODE_ENV=cluster node build/back.js", "serv": "node build/back.js", "insert:dev": "node build/script-insert-dataset.js --datasets=DATA-TEST-STIX2_v2,poisonivy", @@ -51,16 +51,16 @@ "dependencies": { "@apollo/server": "5.2.0", "@as-integrations/express5": "1.1.2", - "@aws-sdk/client-s3": "3.955.0", - "@aws-sdk/client-sts": "3.955.0", - "@aws-sdk/credential-provider-node": "3.955.0", - "@aws-sdk/lib-storage": "3.955.0", + "@aws-sdk/client-s3": "3.970.0", + "@aws-sdk/client-sts": "3.970.0", + "@aws-sdk/credential-provider-node": "3.970.0", + "@aws-sdk/lib-storage": "3.970.0", "@datadog/pprof": "5.13.1", "@elastic/elasticsearch": "8.19.1", - "@escape.tech/graphql-armor": "3.1.7", - "@graphql-tools/merge": "9.1.6", - "@graphql-tools/schema": "10.0.30", - "@graphql-tools/utils": "10.11.0", + "@escape.tech/graphql-armor": "3.2.0", + "@graphql-tools/merge": "9.1.7", + "@graphql-tools/schema": "10.0.31", + "@graphql-tools/utils": "11.0.0", "@jorgeferrero/stream-to-buffer": "2.0.6", "@langchain/core": "0.3.59", "@langchain/mistralai": "0.2.0", @@ -96,7 +96,7 @@ "axios-cookiejar-support": "6.0.5", "bcryptjs": "3.0.3", "bluebird": "3.7.2", - "body-parser": "2.2.1", + "body-parser": "2.2.2", "canonicalize": "2.1.0", "compression": "1.8.1", "content-disposition": "1.0.1", @@ -104,13 +104,13 @@ "cors": "2.8.5", "csv-parse": "6.1.0", "dataloader": "2.2.3", - "ejs": "3.1.10", + "ejs": "4.0.1", "eventsource": "2.0.2", "express": "5.2.1", "express-rate-limit": "8.2.1", "express-session": "1.18.2", "fast-json-patch": "3.1.1", - "file-type": "21.1.1", + "file-type": "21.3.0", "github-api": "3.4.0", "graphql": "16.12.0", "graphql-constraint-directive": "6.0.0", @@ -142,11 +142,11 @@ "node-calls-python": "1.11.1", "node-fetch": "3.3.2", "node-forge": "1.3.3", - "nodemailer": "7.0.11", + "nodemailer": "7.0.12", "openai": "4.104.0", "openid-client": "5.7.1", "opentelemetry-node-metrics": "3.0.0", - "otplib": "12.0.1", + "otplib": "13.1.1", "passport": "0.7.0", "passport-facebook": "3.0.0", "passport-github": "1.1.0", @@ -162,23 +162,23 @@ "tough-cookie": "6.0.0", "turndown": "7.2.2", "unzipper": "0.12.3", - "uuid": "11.1.0", + "uuid": "13.0.0", "uuid-time": "1.0.0", "validator": "13.15.26", "winston": "3.19.0", "winston-daily-rotate-file": "5.0.0", - "ws": "8.18.3", + "ws": "8.19.0", "xml2js": "0.6.2", "zod": "3.25.76" }, "devDependencies": { "@eslint/js": "9.39.2", - "@graphql-codegen/cli": "6.1.0", + "@graphql-codegen/cli": "6.1.1", "@graphql-codegen/introspection": "5.0.0", "@graphql-codegen/typescript-resolvers": "5.1.5", "@luckycatfactory/esbuild-graphql-loader": "3.8.1", "@rollup/plugin-graphql": "2.0.5", - "@stylistic/eslint-plugin": "5.6.1", + "@stylistic/eslint-plugin": "5.7.0", "@types/archiver": "7.0.0", "@types/bluebird": "3.5.42", "@types/content-type": "1.1.9", @@ -201,10 +201,10 @@ "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-import": "2.32.0", "eslint-plugin-import-newlines": "1.4.0", - "globals": "16.5.0", + "globals": "17.0.0", "graphql-tag": "2.12.6", "typescript": "5.9.3", - "typescript-eslint": "8.50.0", + "typescript-eslint": "8.53.0", "vitest": "3.2.4" }, "resolutions": { @@ -212,6 +212,7 @@ "chokidar": "5.0.0", "cookie": "1.1.1", "cross-fetch": "4.1.0", + "ioredis": "5.8.2", "jose": "5.10.0", "json5": "2.2.3", "@datadog/pprof": "patch:@datadog/pprof@5.13.1#./patch/@datadog-pprof.patch", diff --git a/opencti-platform/opencti-graphql/src/config/conf.js b/opencti-platform/opencti-graphql/src/config/conf.js index b8838fc24b73..88363839e669 100644 --- a/opencti-platform/opencti-graphql/src/config/conf.js +++ b/opencti-platform/opencti-graphql/src/config/conf.js @@ -1,4 +1,4 @@ -import { lstatSync, readFileSync } from 'node:fs'; +import { lstatSync, readFileSync, existsSync } from 'node:fs'; import path from 'node:path'; import nconf from 'nconf'; import * as R from 'ramda'; @@ -86,7 +86,13 @@ const { timestamp } = format; const currentPath = process.env.INIT_CWD || process.cwd(); const resolvePath = (relativePath) => path.join(currentPath, relativePath); export const environment = nconf.get('env') || nconf.get('node_env') || process.env.NODE_ENV || DEFAULT_ENV; -const resolveEnvFile = (env) => path.join(resolvePath('config'), `${env.toLowerCase()}.json`); +const resolveEnvFile = (env) => { + const filePath = path.join(resolvePath('config'), `${env.toLowerCase()}.json`); + if (env.toLowerCase() === 'dev' && !existsSync(filePath)) { + return path.join(resolvePath('config'), 'development.json'); + } + return filePath; +}; export const DEV_MODE = environment !== 'production'; const externalConfigurationFile = nconf.get('conf'); export const NODE_INSTANCE_ID = nconf.get('app:node_identifier') || uuid(); @@ -609,10 +615,6 @@ export const BUS_TOPICS = { ADDED_TOPIC: `${TOPIC_PREFIX}DRAFT_WORKSPACE_ADDED_TOPIC`, DELETE_TOPIC: `${TOPIC_PREFIX}DRAFT_WORKSPACE_DELETE_TOPIC`, }, - [M.ENTITY_TYPE_LABEL]: { - EDIT_TOPIC: `${TOPIC_PREFIX}LABEL_EDIT_TOPIC`, - ADDED_TOPIC: `${TOPIC_PREFIX}LABEL_ADDED_TOPIC`, - }, [ENTITY_TYPE_VOCABULARY]: { EDIT_TOPIC: `${TOPIC_PREFIX}VOCABULARY_EDIT_TOPIC`, ADDED_TOPIC: `${TOPIC_PREFIX}VOCABULARY_ADDED_TOPIC`, diff --git a/opencti-platform/opencti-graphql/src/database/cache.ts b/opencti-platform/opencti-graphql/src/database/cache.ts index e1c637492bc3..b35e2fae9877 100644 --- a/opencti-platform/opencti-graphql/src/database/cache.ts +++ b/opencti-platform/opencti-graphql/src/database/cache.ts @@ -6,7 +6,7 @@ import { UnsupportedError } from '../config/errors'; import { telemetry } from '../config/tracing'; import type { AuthContext, AuthUser } from '../types/user'; import type { StixId, StixObject } from '../types/stix-2-1-common'; -import { ENTITY_TYPE_CONNECTOR, ENTITY_TYPE_STREAM_COLLECTION } from '../schema/internalObject'; +import { ENTITY_TYPE_CONNECTOR, ENTITY_TYPE_STATUS, ENTITY_TYPE_STATUS_TEMPLATE, ENTITY_TYPE_STREAM_COLLECTION } from '../schema/internalObject'; import { ENTITY_TYPE_RESOLVED_FILTERS } from '../schema/stixDomainObject'; import { ENTITY_TYPE_TRIGGER } from '../modules/notification/notification-types'; import { ENTITY_TYPE_PLAYBOOK } from '../modules/playbook/playbook-types'; @@ -14,15 +14,20 @@ import { type BasicStoreEntityPublicDashboard, ENTITY_TYPE_PUBLIC_DASHBOARD } fr import { wait } from './utils'; import { ENTITY_TYPE_PIR } from '../modules/pir/pir-types'; import { ENTITY_TYPE_DECAY_EXCLUSION_RULE } from '../modules/decayRule/exclusions/decayExclusionRule-types'; +import { ENTITY_TYPE_LABEL, ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; const STORE_ENTITIES_LINKS: Record = { - // Resolved Filters in cache must be reset depending on connector/stream/triggers/playbooks/Pir modifications + // Resolved Filters in cache must be reset depending on connector/stream/triggers/playbooks/Pir/label modifications [ENTITY_TYPE_STREAM_COLLECTION]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_TRIGGER]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_PLAYBOOK]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_CONNECTOR]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_PIR]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_DECAY_EXCLUSION_RULE]: [ENTITY_TYPE_RESOLVED_FILTERS], + [ENTITY_TYPE_LABEL]: [ENTITY_TYPE_RESOLVED_FILTERS], + [ENTITY_TYPE_MARKING_DEFINITION]: [ENTITY_TYPE_RESOLVED_FILTERS], + // Status must be reset depending on status template modifications + [ENTITY_TYPE_STATUS_TEMPLATE]: [ENTITY_TYPE_STATUS], }; const cache: any = {}; @@ -151,7 +156,6 @@ export const getEntitiesListFromCache = async ; const result: T[] = []; - // eslint-disable-next-line no-restricted-syntax for (const value of map.values()) { result.push(value); } diff --git a/opencti-platform/opencti-graphql/src/database/data-builder.js b/opencti-platform/opencti-graphql/src/database/data-builder.js index 5e57c464819a..e0d5c1f98b7f 100644 --- a/opencti-platform/opencti-graphql/src/database/data-builder.js +++ b/opencti-platform/opencti-graphql/src/database/data-builder.js @@ -42,7 +42,11 @@ export const buildEntityData = async (context, user, input, type, opts = {}) => R.assoc('entity_type', type), R.assoc('creator_id', [user.internal_id]), R.dissoc('update'), + R.dissoc('upsertOperations'), R.dissoc('file'), + R.dissoc('fileMarkings'), + R.dissoc('files'), + R.dissoc('filesMarkings'), R.omit(schemaRelationsRefDefinition.getInputNames(input.entity_type)), )(input); if (inferred) { diff --git a/opencti-platform/opencti-graphql/src/database/engine.ts b/opencti-platform/opencti-graphql/src/database/engine.ts index f1f5ff6df24a..ef5c6fff102f 100644 --- a/opencti-platform/opencti-graphql/src/database/engine.ts +++ b/opencti-platform/opencti-graphql/src/database/engine.ts @@ -139,7 +139,7 @@ import { RELATION_TYPE_SUBFILTER, TYPE_FILTER, } from '../utils/filtering/filtering-constants'; -import { type FilterGroup, FilterMode, FilterOperator } from '../generated/graphql'; +import { type Filter, type FilterGroup, FilterMode, FilterOperator } from '../generated/graphql'; import { type AttributeDefinition, authorizedMembers, @@ -173,7 +173,7 @@ import { ENTITY_IPV4_ADDR, ENTITY_IPV6_ADDR, isStixCyberObservable } from '../sc import { lockResources } from '../lock/master-lock'; import { DRAFT_OPERATION_CREATE, DRAFT_OPERATION_DELETE, DRAFT_OPERATION_DELETE_LINKED, DRAFT_OPERATION_UPDATE_LINKED } from '../modules/draftWorkspace/draftOperations'; import { RELATION_SAMPLE } from '../modules/malwareAnalysis/malwareAnalysis-types'; -import { asyncFilter, asyncMap } from '../utils/data-processing'; +import { asyncMap } from '../utils/data-processing'; import { doYield } from '../utils/eventloop-utils'; import { RELATION_COVERED } from '../modules/securityCoverage/securityCoverage-types'; import type { AuthContext, AuthUser } from '../types/user'; @@ -193,6 +193,7 @@ import type { import type { BasicStoreSettings } from '../types/settings'; import { completeSpecialFilterKeys } from '../utils/filtering/filtering-completeSpecialFilterKeys'; import { IDS_ATTRIBUTES } from '../domain/attribute-utils'; +import type { FiltersWithNested } from './middleware-loader'; const ELK_ENGINE = 'elk'; const OPENSEARCH_ENGINE = 'opensearch'; @@ -621,7 +622,6 @@ export const buildDataRestrictions = async ( opts: { includeAuthorities?: boolean | null } | null | undefined = {}, ): Promise<{ must: any[]; must_not: any[] }> => { const must: any[] = []; - const must_not: any[] = []; // If internal users of the system, we cancel rights checking if (INTERNAL_USERS[user.id]) { @@ -2506,42 +2506,67 @@ export const buildLocalMustFilter = (validFilter: any) => { throw UnsupportedError('Invalid filter configuration', validFilter); }; +const POST_FILTER_TAG_SEPARATOR = ';'; const buildSubQueryForFilterGroup = ( context: AuthContext, user: AuthUser, - inputFilters: any, -) => { + inputFilters: FilterGroup, +): { subQuery: any; postFiltersTags: Set } => { const { mode = 'and', filters = [], filterGroups = [] } = inputFilters; - const localMustFilters: any = []; + const localSubQueries: { subQuery: any; associatedTags: Set }[] = []; + const localPostFilterTags = new Set(); // Handle filterGroups for (let index = 0; index < filterGroups.length; index += 1) { const group = filterGroups[index]; if (isFilterGroupNotEmpty(group)) { - const subQuery = buildSubQueryForFilterGroup(context, user, group); + const { subQuery, postFiltersTags } = buildSubQueryForFilterGroup(context, user, group); if (subQuery) { // can be null - localMustFilters.push(subQuery); + localSubQueries.push({ subQuery, associatedTags: postFiltersTags }); } + postFiltersTags.forEach((t: string) => localPostFilterTags.add(t)); } } // Handle filters for (let index = 0; index < filters.length; index += 1) { - const filter = filters[index]; - const isValidFilter = filter?.values || filter?.nested?.length > 0; + const filter = filters[index] as FiltersWithNested & { postFilteringTag?: string }; + const isValidFilter = filter.values || (filter.nested && filter.nested?.length > 0); if (isValidFilter) { const localMustFilter = buildLocalMustFilter(filter); - localMustFilters.push(localMustFilter); + if (filter.postFilteringTag) { + const associatedTag = filter.postFilteringTag; + localPostFilterTags.add(associatedTag); + const associatedTags = new Set([associatedTag]); + localSubQueries.push({ subQuery: localMustFilter, associatedTags }); + } else { + localSubQueries.push({ subQuery: localMustFilter, associatedTags: new Set() }); + } } } - if (localMustFilters.length > 0) { - return { - bool: { - should: localMustFilters, - minimum_should_match: mode === 'or' ? 1 : localMustFilters.length, - }, - }; - } - return null; + + // Wrap every tagged subquery in a bool must with _name tag + const localMustFilters = localSubQueries.map(({ subQuery, associatedTags }) => { + const tagsToApply = mode === 'or' ? [...localPostFilterTags].filter((t: string) => !associatedTags.has(t)) : []; + if (tagsToApply.length > 0) { + return { + bool: { + must: [subQuery], + ['_name']: tagsToApply.join(POST_FILTER_TAG_SEPARATOR), + }, + }; + } + return subQuery; + }); + + const currentSubQuery = localMustFilters.length > 0 + ? { + bool: { + should: localMustFilters, + minimum_should_match: mode === 'or' ? 1 : localMustFilters.length, + } } + : null; + return { subQuery: currentSubQuery, postFiltersTags: localPostFilterTags }; }; + const getRuntimeEntities = async (context: AuthContext, user: AuthUser, entityType: string) => { const elements = await elPaginate(context, user, READ_INDEX_STIX_DOMAIN_OBJECTS, { types: [entityType], @@ -2736,6 +2761,7 @@ type QueryBodyBuilderOpts = ProcessSearchArgs & BuildDraftFilterOpts & { search?: string | null; filters?: FilterGroup | null; noFiltersChecking?: boolean; + noRegardingOfFilterIdsCheck?: boolean; startDate?: any; endDate?: any; dateAttribute?: string | null; @@ -2761,6 +2787,7 @@ const elQueryBodyBuilder = async (context: AuthContext, user: AuthUser, options: endDate = null, dateAttribute = null, includeAuthorities = false, + noRegardingOfFilterIdsCheck = false, } = options; const elFindByIdsToMap = async (c: AuthContext, u: AuthUser, i: string[], o: any) => { return elFindByIds(c, u, i, { ...o, toMap: true }) as Promise>; @@ -2789,21 +2816,21 @@ const elQueryBodyBuilder = async (context: AuthContext, user: AuthUser, options: specialFiltersContent.push({ key: TYPE_FILTER, values: R.flatten(types) }); } } - const completeFilters: any = specialFiltersContent.length > 0 ? { + const completeFilters = specialFiltersContent.length > 0 ? { mode: FilterMode.And, filters: specialFiltersContent, filterGroups: isFilterGroupNotEmpty(convertedFilters) ? [convertedFilters as FilterGroup] : [], } : convertedFilters; // Handle filters - if (isFilterGroupNotEmpty(completeFilters)) { - const finalFilters = await completeSpecialFilterKeys(context, user, completeFilters); - const filtersSubQuery = buildSubQueryForFilterGroup(context, user, finalFilters); + if (completeFilters && isFilterGroupNotEmpty(completeFilters)) { + const finalFilters = await completeSpecialFilterKeys(context, user, completeFilters, { noRegardingOfFilterIdsCheck }); + const { subQuery: filtersSubQuery } = buildSubQueryForFilterGroup(context, user, finalFilters); if (filtersSubQuery) { mustFilters.push(filtersSubQuery); } } // Handle search - const orderConfiguration = isEmptyField(orderBy) ? [] : orderBy as string; + const orderConfiguration = isEmptyField(orderBy) ? [] : orderBy; const orderCriterion = Array.isArray(orderConfiguration) ? orderConfiguration : [orderConfiguration]; let scoreSearchOrder = orderMode; if (search !== null && search.length > 0) { @@ -2899,6 +2926,37 @@ const buildSearchResult = ( } return elements; }; + +const tagFiltersForPostFiltering = (filters: FilterGroup | undefined | null) => { + const taggedFilters: (Filter & { postFilteringTag: string })[] = filters + ? extractFiltersFromGroup(filters, [INSTANCE_REGARDING_OF, INSTANCE_DYNAMIC_REGARDING_OF]) + .filter((filter) => isEmptyField(filter.operator) || filter.operator === 'eq') + .map((filter, i) => { + const taggedFilter = filter as Filter & { postFilteringTag: string }; + taggedFilter.postFilteringTag = `${i}`; + return taggedFilter; + }) + : []; + + if (taggedFilters) { + return async (context: AuthContext, user: AuthUser, elementsIds: string[]) => { + const postFilters: { tag: string; postFilter: (element: T) => boolean }[] = []; + for (let i = 0; i < taggedFilters.length; i++) { + const taggedFilter = taggedFilters[i]; + postFilters.push({ + tag: taggedFilter.postFilteringTag, + postFilter: await buildRegardingOfFilter(context, user, elementsIds, taggedFilter), + }); + } + return (element: T, tagsToIgnore: Set) => + postFilters + .filter(({ tag }) => !tagsToIgnore.has(tag)) + .every(({ postFilter }) => postFilter(element)); + }; + } + return undefined; +}; + export type PaginateOpts = QueryBodyBuilderOpts & { baseData?: boolean; baseFields?: string[]; @@ -2930,9 +2988,11 @@ export const elPaginate = async ( types = null, withResultMeta = false, first = ES_DEFAULT_PAGINATION, - filters, connectionFormat = true, + noRegardingOfFilterIdsCheck = false, } = options; + // tagFiltersForPostFiltering have side effect on options.filters, it must be done before elQueryBodyBuilder + const createPostFilter = tagFiltersForPostFiltering(options.filters); const body = await elQueryBodyBuilder(context, user, options); if (body.size > ES_MAX_PAGINATION && !bypassSizeLimit) { logApp.info('[SEARCH] Pagination limited to max result config', { size: body.size, max: ES_MAX_PAGINATION }); @@ -2952,15 +3012,20 @@ export const elPaginate = async ( } logApp.debug('[SEARCH] paginate', { query }); try { - const data = await elRawSearch(context, user, types !== null ? types : 'Any', query); - const globalCount = data.hits.total.value; - const elements = await elConvertHits(data.hits.hits); - // If filters contains an "in regards of" filter a post-security filtering is needed - - const regardingOfFilter = elements.length === 0 ? undefined : await buildRegardingOfFilter(context, user, elements, filters); - const filteredElements = regardingOfFilter ? await asyncFilter(elements, regardingOfFilter) : elements; - const filterCount = elements.length - filteredElements.length; - const result = buildSearchResult(filteredElements, first, body.search_after, globalCount, filterCount, connectionFormat); + const { hits: { hits, total: { value: globalCount } } } = await elRawSearch(context, user, types !== null ? types : 'Any', query); + const elements = await elConvertHits(hits); + let finalElements = elements; + if (!noRegardingOfFilterIdsCheck && finalElements.length > 0 && createPostFilter) { + // Since filters contains filters requiring post filtering (regardingOf, dynamicRegardingOf), a post-security filtering is needed + const postFilter = await createPostFilter(context, user, elements.map(({ id }) => id)); + finalElements = elements.filter((element, i) => { + const dataHit = hits[i]; + const tagsToIgnoreSet = new Set((dataHit.matched_queries ?? []).flatMap((matchedQuery: string) => matchedQuery.split(POST_FILTER_TAG_SEPARATOR))); + return postFilter(element, tagsToIgnoreSet); + }); + } + const filterCount = elements.length - finalElements.length; + const result = buildSearchResult(finalElements, first, body.search_after, globalCount, filterCount, connectionFormat); if (withResultMeta) { const lastProcessedSort = R.last(elements)?.sort; const endCursor = lastProcessedSort ? offsetToCursor(lastProcessedSort) : null; @@ -3484,99 +3549,87 @@ export const elAggregationsList = async ( const buildRegardingOfFilter = async ( context: AuthContext, user: AuthUser, - elements: T[], - filters: FilterGroup | undefined | null, + elementIds: string[], + filter: Filter, ) => { - // First check if there is an "in regards of" filter - // If its case we need to ensure elements are filtered according to denormalization rights. - if (isNotEmptyField(filters)) { - const definedFilters = filters as FilterGroup; - const extractedFilters = extractFiltersFromGroup(definedFilters, [INSTANCE_REGARDING_OF, INSTANCE_DYNAMIC_REGARDING_OF]) - .filter((filter) => isEmptyField(filter.operator) || filter.operator === 'eq'); - if (extractedFilters.length > 0) { - const targetValidatedIds = new Set(); - const sideIdManualInferred = new Map(); - for (let i = 0; i < extractedFilters.length; i += 1) { - const { values } = extractedFilters[i]; - const ids = values.filter((v) => v.key === ID_SUBFILTER).map((f) => f.values).flat(); - const types = values.filter((v) => v.key === RELATION_TYPE_SUBFILTER).map((f) => f.values).flat(); - const inferredParameterValues = values.filter((v) => v.key === RELATION_INFERRED_SUBFILTER).map((f) => f.values).flat(); - const directionForced = R.head(values.filter((v) => v.key === INSTANCE_REGARDING_OF_DIRECTION_FORCED).map((f) => f.values).flat()) ?? false; - const directionReverse = R.head(values.filter((v) => v.key === INSTANCE_REGARDING_OF_DIRECTION_REVERSE).map((f) => f.values).flat()) ?? false; - // resolve all relationships that target the id values, forcing the type is available - const paginateArgs: RepaginateOpts = { baseData: true, types }; - const elementIds = elements.map(({ id }) => id); - if (directionForced) { - // If a direction is forced, build the filter in the correct direction - const directedFilters = []; - if (directionReverse) { - directedFilters.push({ key: ['fromId'], values: elementIds }); - if (ids.length > 0) { // Ids can be empty if nothing configured by the user - directedFilters.push({ key: ['toId'], values: ids }); - } - } else { - directedFilters.push({ key: ['toId'], values: elementIds }); - if (ids.length > 0) { // Ids can be empty if nothing configured by the user - directedFilters.push({ key: ['fromId'], values: ids }); - } - } - paginateArgs.filters = { mode: FilterMode.And, filters: directedFilters, filterGroups: [] }; - } else { - // If no direction is setup, create the filter group for both directions - const filterTo = [{ key: ['fromId'], values: elementIds }]; - const filterFrom = [{ key: ['toId'], values: elementIds }]; - if (ids.length > 0) { // Ids can be empty if nothing configured by the user - filterTo.push({ key: ['toId'], values: ids }); - filterFrom.push({ key: ['fromId'], values: ids }); - } - paginateArgs.filters = { - mode: FilterMode.Or, - filters: [], - filterGroups: [ - { mode: FilterMode.And, filterGroups: [], filters: filterTo }, - { mode: FilterMode.And, filterGroups: [], filters: filterFrom }], - }; - } - let relationshipIndices = READ_RELATIONSHIPS_INDICES; - if (inferredParameterValues.length > 0) { - if (inferredParameterValues.includes('true')) { - relationshipIndices = [READ_INDEX_INFERRED_RELATIONSHIPS]; - } else if (inferredParameterValues.includes('false')) { - relationshipIndices = READ_RELATIONSHIPS_INDICES_WITHOUT_INFERRED; - }; - }; - const relationships = await elList(context, user, relationshipIndices, paginateArgs); - // compute side ids - const addTypeSide = (sideId: string, sideType: string) => { - targetValidatedIds.add(sideId); - if (sideIdManualInferred.has(sideId)) { - const toTypes = sideIdManualInferred.get(sideId); - toTypes.add(sideType); - sideIdManualInferred.set(sideId, toTypes); - } else { - const toTypes = new Set(); - toTypes.add(sideType); - sideIdManualInferred.set(sideId, toTypes); - } - }; - for (let relIndex = 0; relIndex < relationships.length; relIndex += 1) { - await doYield(); - const relation = relationships[relIndex]; - const relType = isInferredIndex(relation._index) ? 'inferred' : 'manual'; - addTypeSide(relation.fromId, relType); - addTypeSide(relation.toId, relType); - } + // We need to ensure elements are filtered according to denormalization rights. + const targetValidatedIds = new Set(); + const sideIdManualInferred = new Map(); + const { values } = filter; + const ids = values.filter((v) => v.key === ID_SUBFILTER).map((f) => f.values).flat(); + const types = values.filter((v) => v.key === RELATION_TYPE_SUBFILTER).map((f) => f.values).flat(); + const inferredParameterValues = values.filter((v) => v.key === RELATION_INFERRED_SUBFILTER).map((f) => f.values).flat(); + const directionForced = R.head(values.filter((v) => v.key === INSTANCE_REGARDING_OF_DIRECTION_FORCED).map((f) => f.values).flat()) ?? false; + const directionReverse = R.head(values.filter((v) => v.key === INSTANCE_REGARDING_OF_DIRECTION_REVERSE).map((f) => f.values).flat()) ?? false; + // resolve all relationships that target the id values, forcing the type is available + const paginateArgs: RepaginateOpts = { baseData: true, types }; + if (directionForced) { + // If a direction is forced, build the filter in the correct direction + const directedFilters = []; + if (directionReverse) { + directedFilters.push({ key: ['fromId'], values: elementIds }); + if (ids.length > 0) { // Ids can be empty if nothing configured by the user + directedFilters.push({ key: ['toId'], values: ids }); } - return (element: (T & { regardingOfTypes?: string })) => { - const accepted = targetValidatedIds.has(element.id); - if (accepted) { - element.regardingOfTypes = sideIdManualInferred.get(element.id); - } - return accepted; - }; + } else { + directedFilters.push({ key: ['toId'], values: elementIds }); + if (ids.length > 0) { // Ids can be empty if nothing configured by the user + directedFilters.push({ key: ['fromId'], values: ids }); + } + } + paginateArgs.filters = { mode: FilterMode.And, filters: directedFilters, filterGroups: [] }; + } else { + // If no direction is setup, create the filter group for both directions + const filterTo = [{ key: ['fromId'], values: elementIds }]; + const filterFrom = [{ key: ['toId'], values: elementIds }]; + if (ids.length > 0) { // Ids can be empty if nothing configured by the user + filterTo.push({ key: ['toId'], values: ids }); + filterFrom.push({ key: ['fromId'], values: ids }); } + paginateArgs.filters = { + mode: FilterMode.Or, + filters: [], + filterGroups: [ + { mode: FilterMode.And, filterGroups: [], filters: filterTo }, + { mode: FilterMode.And, filterGroups: [], filters: filterFrom }], + }; } - return undefined; + let relationshipIndices = READ_RELATIONSHIPS_INDICES; + if (inferredParameterValues.length > 0) { + if (inferredParameterValues.includes('true')) { + relationshipIndices = [READ_INDEX_INFERRED_RELATIONSHIPS]; + } else if (inferredParameterValues.includes('false')) { + relationshipIndices = READ_RELATIONSHIPS_INDICES_WITHOUT_INFERRED; + } + } + const relationships = await elList(context, user, relationshipIndices, paginateArgs); + // compute side ids + const addTypeSide = (sideId: string, sideType: string) => { + targetValidatedIds.add(sideId); + if (sideIdManualInferred.has(sideId)) { + const toTypes = sideIdManualInferred.get(sideId); + toTypes.add(sideType); + sideIdManualInferred.set(sideId, toTypes); + } else { + const toTypes = new Set(); + toTypes.add(sideType); + sideIdManualInferred.set(sideId, toTypes); + } + }; + for (let relIndex = 0; relIndex < relationships.length; relIndex += 1) { + await doYield(); + const relation = relationships[relIndex]; + const relType = isInferredIndex(relation._index) ? 'inferred' : 'manual'; + addTypeSide(relation.fromId, relType); + addTypeSide(relation.toId, relType); + } + return (element: (T & { regardingOfTypes?: string })) => { + const accepted = targetValidatedIds.has(element.id); + if (accepted) { + element.regardingOfTypes = sideIdManualInferred.get(element.id); + } + return accepted; + }; }; type AttributeValues = { orderMode?: string | null; diff --git a/opencti-platform/opencti-graphql/src/database/middleware-loader.ts b/opencti-platform/opencti-graphql/src/database/middleware-loader.ts index 4860699969d9..0ad4a23cb280 100644 --- a/opencti-platform/opencti-graphql/src/database/middleware-loader.ts +++ b/opencti-platform/opencti-graphql/src/database/middleware-loader.ts @@ -67,6 +67,7 @@ export interface ListFilter { orderMode?: InputMaybe; filters?: FilterGroupWithNested | null; noFiltersChecking?: boolean; + noRegardingOfFilterIdsCheck?: boolean; callback?: (result: Array) => Promise; } diff --git a/opencti-platform/opencti-graphql/src/database/middleware.js b/opencti-platform/opencti-graphql/src/database/middleware.js index e5b4b4d9519b..93b9ae851e3e 100644 --- a/opencti-platform/opencti-graphql/src/database/middleware.js +++ b/opencti-platform/opencti-graphql/src/database/middleware.js @@ -2157,6 +2157,22 @@ export const buildChanges = async (context, user, entityType, inputs) => { return changes; }; +const resolveRefsForInputs = async (context, user, type, updateInputs) => { + // endregion + const metaKeys = [...schemaRelationsRefDefinition.getStixNames(type), ...schemaRelationsRefDefinition.getInputNames(type)]; + const meta = updateInputs.filter((e) => metaKeys.includes(e.key)); + const metaIds = R.uniq(meta.map((i) => i.value ?? []).flat()); + const metaDependencies = await elFindByIds(context, user, metaIds, { toMap: true, mapWithAllIds: true }); + const revolvedInputs = updateInputs.map((input) => { + if (metaKeys.includes(input.key)) { + const resolvedValues = (input.value ?? []).map((refId) => metaDependencies[refId]).filter((o) => isNotEmptyField(o)); + return { ...input, value: resolvedValues }; + } + return input; + }); + return revolvedInputs; +}; + export const updateAttributeMetaResolved = async (context, user, initial, inputs, opts = {}) => { const { locks = [], impactStandardId = true } = opts; const updates = Array.isArray(inputs) ? inputs : [inputs]; @@ -2565,17 +2581,8 @@ export const updateAttributeFromLoadedWithRefs = async (context, user, initial, } const newInputs = adaptUpdateInputsConfidence(user, inputs, initial); // endregion - const metaKeys = [...schemaRelationsRefDefinition.getStixNames(initial.entity_type), ...schemaRelationsRefDefinition.getInputNames(initial.entity_type)]; - const meta = newInputs.filter((e) => metaKeys.includes(e.key)); - const metaIds = R.uniq(meta.map((i) => i.value ?? []).flat()); - const metaDependencies = await elFindByIds(context, user, metaIds, { toMap: true, mapWithAllIds: true }); - const revolvedInputs = newInputs.map((input) => { - if (metaKeys.includes(input.key)) { - const resolvedValues = (input.value ?? []).map((refId) => metaDependencies[refId]).filter((o) => isNotEmptyField(o)); - return { ...input, value: resolvedValues }; - } - return input; - }); + const revolvedInputs = await resolveRefsForInputs(context, user, initial.entity_type, newInputs); + return updateAttributeMetaResolved(context, user, initial, revolvedInputs, opts); }; @@ -2848,6 +2855,11 @@ const upsertElement = async (context, user, element, type, basePatch, opts = {}) const updatePatch = buildUpdatePatchForUpsert(user, resolvedElement, type, basePatch, confidenceForUpsert); + // upsertOperations : resolve refs + if (updatePatch.upsertOperations?.length > 0) { + updatePatch.upsertOperations = await resolveRefsForInputs(context, user, type, updatePatch.upsertOperations); + } + const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS); const validEnterpriseEdition = settings.valid_enterprise_edition; // All inputs impacted by modifications (+inner) @@ -3338,24 +3350,43 @@ const createEntityRaw = async (context, user, rawInput, type, opts = {}) => { } // Create the object const dataEntity = await buildEntityData(context, user, resolvedInput, type, opts); - // If file directly attached + // Handle multiple files upload (new plural form) + const filesToUpload = []; + if (!isEmptyField(resolvedInput.files) && Array.isArray(resolvedInput.files)) { + const filesMarkings = resolvedInput.filesMarkings || []; + for (let i = 0; i < resolvedInput.files.length; i += 1) { + const fileInput = resolvedInput.files[i]; + const fileMarking = filesMarkings[i] || resolvedInput.objectMarking?.map(({ id }) => id); + filesToUpload.push({ file: fileInput, markings: fileMarking }); + } + } + // Handle single file upload (backward compatibility) if (!isEmptyField(resolvedInput.file)) { - const { filename } = await resolvedInput.file; + const file_markings = isNotEmptyField(resolvedInput.fileMarkings) ? resolvedInput.fileMarkings : resolvedInput.objectMarking?.map(({ id }) => id); + filesToUpload.push({ file: resolvedInput.file, markings: file_markings }); + } + // Process all files to upload + if (filesToUpload.length > 0) { const isAutoExternal = entitySetting?.platform_entity_files_ref; const path = `import/${type}/${dataEntity.element[ID_INTERNAL]}`; - const key = `${path}/${filename}`; - const meta = isAutoExternal ? { external_reference_id: generateStandardId(ENTITY_TYPE_EXTERNAL_REFERENCE, { url: `/storage/get/${key}` }) } : {}; - const file_markings = resolvedInput.objectMarking?.map(({ id }) => id); - const { upload: file } = await uploadToStorage(context, user, path, input.file, { entity: dataEntity.element, file_markings, meta }); - dataEntity.element = { ...dataEntity.element, x_opencti_files: [storeFileConverter(user, file)] }; - // Add external references from files if necessary - if (isAutoExternal) { - // Create external ref + link to current entity - const createExternal = { source_name: file.name, url: `/storage/get/${file.id}`, fileId: file.id }; - const externalRef = await createEntity(context, user, createExternal, ENTITY_TYPE_EXTERNAL_REFERENCE); - const newRefRel = buildInnerRelation(dataEntity.element, externalRef, RELATION_EXTERNAL_REFERENCE); - dataEntity.relations.push(...newRefRel); + const uploadedFiles = []; + for (let i = 0; i < filesToUpload.length; i += 1) { + const { file: fileInput, markings: file_markings } = filesToUpload[i]; + const { filename } = await fileInput; + const key = `${path}/${filename}`; + const meta = isAutoExternal ? { external_reference_id: generateStandardId(ENTITY_TYPE_EXTERNAL_REFERENCE, { url: `/storage/get/${key}` }) } : {}; + const { upload: uploadedFile } = await uploadToStorage(context, user, path, fileInput, { entity: dataEntity.element, file_markings, meta }); + uploadedFiles.push(storeFileConverter(user, uploadedFile)); + // Add external references from files if necessary + if (isAutoExternal) { + // Create external ref + link to current entity + const createExternal = { source_name: uploadedFile.name, url: `/storage/get/${uploadedFile.id}`, fileId: uploadedFile.id }; + const externalRef = await createEntity(context, user, createExternal, ENTITY_TYPE_EXTERNAL_REFERENCE); + const newRefRel = buildInnerRelation(dataEntity.element, externalRef, RELATION_EXTERNAL_REFERENCE); + dataEntity.relations.push(...newRefRel); + } } + dataEntity.element = { ...dataEntity.element, x_opencti_files: uploadedFiles }; } if (opts.restore === true) { dataMessage = generateRestoreMessage(dataEntity.element); diff --git a/opencti-platform/opencti-graphql/src/database/migration.js b/opencti-platform/opencti-graphql/src/database/migration.js index 6785b6bda79d..1f47a7b24b7a 100644 --- a/opencti-platform/opencti-graphql/src/database/migration.js +++ b/opencti-platform/opencti-graphql/src/database/migration.js @@ -2,14 +2,16 @@ import * as R from 'ramda'; import { MigrationSet } from 'migrate'; import Migration from 'migrate/lib/migration'; import { logApp, logMigration, PLATFORM_VERSION } from '../config/conf'; -import { DatabaseError } from '../config/errors'; +import { DatabaseError, FunctionalError } from '../config/errors'; import { RELATION_MIGRATES } from '../schema/internalRelationship'; import { ENTITY_TYPE_MIGRATION_REFERENCE, ENTITY_TYPE_MIGRATION_STATUS } from '../schema/internalObject'; import { createEntity, createRelation, loadEntity, patchAttribute } from './middleware'; import { executionContext, SYSTEM_USER } from '../utils/access'; -// eslint-disable-next-line import/extensions,import/no-unresolved -import migrations, { filenames as migrationsFilenames } from '../migrations/*.js'; import { fullEntitiesThroughRelationsToList } from './middleware-loader'; +import fs from 'fs/promises'; +import path from 'path'; + +const MIGRATION_DIRECTORY_PATH = path.join(__dirname, '../src/migrations'); const normalizeMigrationName = (rawName) => { if (rawName.startsWith('./')) { @@ -18,21 +20,37 @@ const normalizeMigrationName = (rawName) => { return rawName; }; -const retrieveMigrations = () => { - const knexMigrations = migrations.map((migration, i) => ({ - name: migrationsFilenames[i].substring('../migrations/'.length), - migration, - })); - return knexMigrations.map(({ name, migration }) => { - const title = normalizeMigrationName(name); - const [time] = title.split('-'); - const timestamp = parseInt(time, 10); - return { title, up: migration.up, down: migration.down, timestamp }; +const buildMigrationObject = (migration, name) => { + const title = normalizeMigrationName(name); + const [time] = title.split('-'); + const timestamp = parseInt(time, 10); + return { title, timestamp, up: migration.up, down: migration.down }; +}; + +export const retrieveMigration = async (migrationFileName) => { + try { + const migration = await import(`../migrations/${migrationFileName}.js`); + return buildMigrationObject(migration, `${migrationFileName}.js`); + } catch (e) { + throw FunctionalError('No migration found with this name', { cause: e, migrationFileName }); + } +}; + +const retrieveMigrations = async () => { + const migrationsFilenames = (await fs.readdir(MIGRATION_DIRECTORY_PATH)) + .filter((f) => f.endsWith('.js')); + + const migrations = await Promise.all(migrationsFilenames + .map((file) => import(`../migrations/${file.replace('.js', '')}.js`))); // .js removed and added because extension require in vite dynamic var imports + + return migrations.map((migration, i) => { + const name = migrationsFilenames[i]; + return buildMigrationObject(migration, name); }); }; -export const lastAvailableMigrationTime = () => { - const allMigrations = retrieveMigrations(); +export const lastAvailableMigrationTime = async () => { + const allMigrations = await retrieveMigrations(); const lastMigration = R.last(allMigrations); return lastMigration && lastMigration.timestamp; }; @@ -89,17 +107,17 @@ const migrationStorage = { }, }; -export const applyMigration = (context) => { +export const applyMigration = async (context) => { const set = new MigrationSet(migrationStorage); return new Promise((resolve, reject) => { - migrationStorage.load((err, state) => { + migrationStorage.load(async (err, state) => { if (err) { throw DatabaseError('[MIGRATION] Error applying migration', { cause: err }); } // Set last run date on the set set.lastRun = state.lastRun; // Read migrations from webpack - const filesMigrationSet = retrieveMigrations(); + const filesMigrationSet = await retrieveMigrations(); // Filter migration to apply. Should be > lastRun const [lastMigrationTime] = state.lastRun.split('-'); const lastMigrationDate = new Date(parseInt(lastMigrationTime, 10)); @@ -136,7 +154,7 @@ export const applyMigration = (context) => { reject(reason); }); }).then(async (state) => { - // After migration, path the current version runtime + // After migration, patch the current version runtime const statusPatch = { platformVersion: PLATFORM_VERSION }; await patchAttribute(context, SYSTEM_USER, state.internal_id, ENTITY_TYPE_MIGRATION_STATUS, statusPatch); logApp.info(`[MIGRATION] Platform version updated to ${PLATFORM_VERSION}`); diff --git a/opencti-platform/opencti-graphql/src/database/rabbitmq.js b/opencti-platform/opencti-graphql/src/database/rabbitmq.js index 2a6976b4e5c9..4e3845952866 100644 --- a/opencti-platform/opencti-graphql/src/database/rabbitmq.js +++ b/opencti-platform/opencti-graphql/src/database/rabbitmq.js @@ -11,6 +11,7 @@ import { getHttpClient } from '../utils/http-client'; import { fullEntitiesList } from './middleware-loader'; import { ENTITY_TYPE_BACKGROUND_TASK, ENTITY_TYPE_CONNECTOR, ENTITY_TYPE_SYNC } from '../schema/internalObject'; import { ENTITY_TYPE_PLAYBOOK } from '../modules/playbook/playbook-types'; +import { s3ConnectionConfig } from './raw-file-storage'; export const CONNECTOR_EXCHANGE = `${RABBIT_QUEUE_PREFIX}amqp.connector.exchange`; export const WORKER_EXCHANGE = `${RABBIT_QUEUE_PREFIX}amqp.worker.exchange`; @@ -47,7 +48,7 @@ const amqpCred = () => { return { credentials: amqp.credentials.plain(USERNAME, PASSWORD) }; }; -export const config = () => { +export const rabbitmqConnectionConfig = () => { return { host: HOSTNAME, vhost: VHOST, @@ -201,8 +202,25 @@ export const getBestBackgroundConnectorId = async (context, user) => { return bestQueue.name.substring(`${RABBIT_QUEUE_PREFIX}push_`.length); }; +export const listenRouting = (connectorId) => `${RABBIT_QUEUE_PREFIX}listen_routing_${connectorId}`; +export const pushRouting = (connectorId) => `${RABBIT_QUEUE_PREFIX}push_routing_${connectorId}`; + +// Dead letter queue routing ID for bundles that are too large. +// NOTE: +// - This constant is used here to build the dead_letter_routing value in connectorConfig. +// - The full CONNECTOR_QUEUE_BUNDLES_TOO_LARGE queue configuration object is defined later +// in this file near the rest of the queue declarations. +const CONNECTOR_QUEUE_BUNDLES_TOO_LARGE_ID = 'too-large-bundle'; + +/** + * Build the complete connector configuration that includes: + * - RabbitMQ connection info + * - S3 connection info + * - Queue routing configuration + */ export const connectorConfig = (id, listen_callback_uri = undefined) => ({ - connection: config(), + connection: rabbitmqConnectionConfig(), + s3: s3ConnectionConfig(), push: `${RABBIT_QUEUE_PREFIX}push_${id}`, push_routing: pushRouting(id), push_exchange: WORKER_EXCHANGE, @@ -210,12 +228,9 @@ export const connectorConfig = (id, listen_callback_uri = undefined) => ({ listen_routing: listenRouting(id), listen_exchange: CONNECTOR_EXCHANGE, listen_callback_uri, - dead_letter_routing: listenRouting(CONNECTOR_QUEUE_BUNDLES_TOO_LARGE.id), + dead_letter_routing: listenRouting(CONNECTOR_QUEUE_BUNDLES_TOO_LARGE_ID), }); -export const listenRouting = (connectorId) => `${RABBIT_QUEUE_PREFIX}listen_routing_${connectorId}`; -export const pushRouting = (connectorId) => `${RABBIT_QUEUE_PREFIX}push_routing_${connectorId}`; - export const registerConnectorQueues = async (id, name, type, scope) => { const listenQueue = `${RABBIT_QUEUE_PREFIX}listen_${id}`; const pushQueue = `${RABBIT_QUEUE_PREFIX}push_${id}`; diff --git a/opencti-platform/opencti-graphql/src/database/raw-file-storage.ts b/opencti-platform/opencti-graphql/src/database/raw-file-storage.ts index d9fe3df5daa1..05bfe9f45095 100644 --- a/opencti-platform/opencti-graphql/src/database/raw-file-storage.ts +++ b/opencti-platform/opencti-graphql/src/database/raw-file-storage.ts @@ -31,6 +31,20 @@ const useAwsLogs = booleanConf('minio:use_aws_logs', false); const disableChecksumValidation = booleanConf('minio:disable_checksum_validation', false); export const defaultValidationMode = conf.get('app:validation_mode'); +/** + * Export S3 connection configuration for connectors. + * This allows connectors to upload bundles directly to S3 storage. + */ +export const s3ConnectionConfig = () => ({ + endpoint: clientEndpoint, + port: clientPort, + use_ssl: useSslConnection, + bucket_name: bucketName, + bucket_region: bucketRegion, + access_key: clientAccessKey, + secret_key: clientSecretKey, +}); + let s3Client: S3Client; // Client reference const buildCredentialProvider = async () => { diff --git a/opencti-platform/opencti-graphql/src/database/session.js b/opencti-platform/opencti-graphql/src/database/session.js index 6adea1985a0f..b469228941e3 100644 --- a/opencti-platform/opencti-graphql/src/database/session.js +++ b/opencti-platform/opencti-graphql/src/database/session.js @@ -82,10 +82,8 @@ export const killSession = async (id) => { }); }; -export const killUserSessions = async (userId) => { +export const killSessions = async (sessionsIds) => { const { store } = applicationSession; - const sessions = await findUserSessions(userId); - const sessionsIds = sessions.map((s) => s.id); const killedSessions = []; for (let index = 0; index < sessionsIds.length; index += 1) { const sessionId = sessionsIds[index]; @@ -96,9 +94,10 @@ export const killUserSessions = async (userId) => { return killedSessions; }; -export const findSessionsForUsers = async (userIds) => { - const sessions = await findSessions(); - return sessions.filter((s) => userIds.includes(s.user_id)).map((s) => s.sessions).flat(); +export const killUserSessions = async (userId) => { + const sessions = await findUserSessions(userId); + const sessionsIds = sessions.map((s) => s.id); + return killSessions(sessionsIds); }; export const applicationSession = createSessionMiddleware(); diff --git a/opencti-platform/opencti-graphql/src/domain/attackPattern.ts b/opencti-platform/opencti-graphql/src/domain/attackPattern.ts index 82e2129c44aa..b09a5e1dd052 100644 --- a/opencti-platform/opencti-graphql/src/domain/attackPattern.ts +++ b/opencti-platform/opencti-graphql/src/domain/attackPattern.ts @@ -1,7 +1,8 @@ +import * as R from 'ramda'; import { createEntity } from '../database/middleware'; import { BUS_TOPICS } from '../config/conf'; import { notify } from '../database/redis'; -import { READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_META_OBJECTS } from '../database/utils'; +import { isEmptyField, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_META_OBJECTS } from '../database/utils'; import { ENTITY_TYPE_ATTACK_PATTERN, ENTITY_TYPE_COURSE_OF_ACTION, ENTITY_TYPE_DATA_COMPONENT } from '../schema/stixDomainObject'; import { ABSTRACT_STIX_DOMAIN_OBJECT } from '../schema/general'; import { RELATION_DETECTS, RELATION_MITIGATES, RELATION_SUBTECHNIQUE_OF } from '../schema/stixCoreRelationship'; @@ -29,7 +30,17 @@ export const findAttackPatternPaginated = (context: AuthContext, user: AuthUser, }; export const addAttackPattern = async (context: AuthContext, user: AuthUser, attackPattern: AttackPatternAddInput) => { - const created = await createEntity(context, user, attackPattern, ENTITY_TYPE_ATTACK_PATTERN); + let xMitreId = null; + if (isEmptyField(attackPattern.x_mitre_id)) { + // Extract x_mitre_id from name if not already provided + // Match patterns like T0015.001, T0015, FT048 + const mitreIdMatch = attackPattern.name?.match(/\b([TF]T?\d+(?:\.\d+)?)\b/); + if (mitreIdMatch) { + xMitreId = mitreIdMatch[1]; + } + } + const attackPatternToCreate = xMitreId ? R.assoc('x_mitre_id', xMitreId, attackPattern) : attackPattern; + const created = await createEntity(context, user, attackPatternToCreate, ENTITY_TYPE_ATTACK_PATTERN); return notify(BUS_TOPICS[ABSTRACT_STIX_DOMAIN_OBJECT].ADDED_TOPIC, created, user); }; diff --git a/opencti-platform/opencti-graphql/src/domain/connector.ts b/opencti-platform/opencti-graphql/src/domain/connector.ts index ef370282df28..9e1dd9277440 100644 --- a/opencti-platform/opencti-graphql/src/domain/connector.ts +++ b/opencti-platform/opencti-graphql/src/domain/connector.ts @@ -42,12 +42,13 @@ import { type RegisterConnectorInput, type RegisterConnectorsManagerInput, type RequestConnectorStatusInput, + type SynchronizerAddAutoUserInput, type SynchronizerAddInput, type SynchronizerFetchInput, type UpdateConnectorManagerStatusInput, ValidationMode, } from '../generated/graphql'; -import { BUS_TOPICS, logApp } from '../config/conf'; +import { BUS_TOPICS, logApp, PLATFORM_VERSION } from '../config/conf'; import { deleteWorkForConnector } from './work'; import { testSync as testSyncUtils } from './connector-utils'; import { defaultValidationMode, loadFile, uploadJobImport } from '../database/file-storage'; @@ -63,7 +64,11 @@ import { addDraftWorkspace } from '../modules/draftWorkspace/draftWorkspace-doma import type { Work } from '../types/work'; import { AxiosError } from 'axios'; import { URL } from 'node:url'; +import { isCompatibleVersionWithMinimal } from '../utils/version'; +import { extractContentFrom } from '../utils/fileToContent'; +import type { FileHandle } from 'fs/promises'; +const MINIMAL_SYNCHRONIZER_COMPATIBLE_VERSION = '6.9.6'; // Sanitize name for K8s/Docker const sanitizeContainerName = (label: string): string => { const withHyphens = label.replace(/([a-z])([A-Z])/g, '$1-$2'); @@ -573,24 +578,94 @@ export const fetchRemoteStreams = async (context: AuthContext, user: AuthUser, i throw ValidationError('Error getting the streams from remote OpenCTI', 'uri', { cause: errorMessage }); } }; -export const registerSync = async (context: AuthContext, user: AuthUser, syncData: SynchronizerAddInput) => { - const data = { ...syncData, running: false }; - await testSyncUtils(context, user, data); - const { element, isCreation } = await createEntity(context, user, data, ENTITY_TYPE_SYNC, { complete: true }); +export const registerSync = async ( + context: AuthContext, + user: AuthUser, + syncData: SynchronizerAddInput, +) => { + let finalSyncData = { ...syncData, running: false }; + + if (finalSyncData.automatic_user) { + const onTheFlyCreatedUser = await createOnTheFlyUser( + context, + user, + { + userName: finalSyncData.user_id, + serviceAccount: true, + confidenceLevel: finalSyncData.confidence_level, + }, + ); + + finalSyncData = { + ...finalSyncData, + user_id: onTheFlyCreatedUser.id, + }; + } + + const { + automatic_user: _automatic_user, + confidence_level: _confidence_level, + ...synchronizerToCreate + } = finalSyncData; + + await testSyncUtils(context, user, synchronizerToCreate); + + const { element, isCreation } = await createEntity( + context, + user, + synchronizerToCreate, + ENTITY_TYPE_SYNC, + { complete: true }, + ); + if (isCreation) { const syncId = element.internal_id; - await registerConnectorQueues(syncId, `Sync ${syncId} queue`, 'internal', 'sync'); + + await registerConnectorQueues( + syncId, + `Sync ${syncId} queue`, + 'internal', + 'sync', + ); + await publishUserAction({ user, event_type: 'mutation', event_scope: 'create', event_access: 'administration', - message: `creates synchronizer \`${syncData.name}\``, - context_data: { id: element.id, entity_type: ENTITY_TYPE_SYNC, input: data }, + message: `creates synchronizer \`${finalSyncData.name}\``, + context_data: { + id: element.id, + entity_type: ENTITY_TYPE_SYNC, + input: synchronizerToCreate, + }, }); } + return element; }; + +export const syncAddInputFromImport = async (file: Promise) => { + const parsedData = await extractContentFrom(file); + + // check platform version compatibility + if (!isCompatibleVersionWithMinimal(parsedData.openCTI_version, MINIMAL_SYNCHRONIZER_COMPATIBLE_VERSION)) { + throw FunctionalError( + `Invalid version of the platform. Please upgrade your OpenCTI. Minimal version required: ${MINIMAL_SYNCHRONIZER_COMPATIBLE_VERSION}`, + { reason: parsedData.openCTI_version }, + ); + } + + return parsedData.configuration; +}; + +export const synchronizerAddAutoUser = async (context: AuthContext, user: AuthUser, synchronizerId: string, input: SynchronizerAddAutoUserInput) => { + const onTheFlyCreatedUser = await createOnTheFlyUser(context, user, + { userName: input.user_name, confidenceLevel: input.confidence_level, serviceAccount: true }); + + return syncEditField(context, user, synchronizerId, [{ key: 'user_id', value: [onTheFlyCreatedUser.id] }]); +}; + export const syncEditField = async (context: AuthContext, user: AuthUser, syncId: string, input: EditInput[]) => { const { element } = await updateAttribute(context, user, syncId, ENTITY_TYPE_SYNC, input); await publishUserAction({ @@ -616,6 +691,23 @@ export const syncDelete = async (context: AuthContext, user: AuthUser, syncId: s }); return syncId; }; +export const synchronizerExport = async (synchronizer: BasicStoreEntitySynchronizer) => { + const { name, uri, stream_id, current_state_date, listen_deletion, ssl_verify, no_dependencies, synchronized } = synchronizer; + return JSON.stringify({ + openCTI_version: PLATFORM_VERSION, + type: 'openCTI_stream', + configuration: { + name, + uri, + stream_id, + current_state_date, + listen_deletion, + ssl_verify, + no_dependencies, + synchronized, + }, + }); +}; export const syncCleanContext = async (context: AuthContext, user: AuthUser, syncId: string) => { await delEditContext(user, syncId); return storeLoadById(context, user, syncId, ENTITY_TYPE_SYNC) diff --git a/opencti-platform/opencti-graphql/src/domain/container.js b/opencti-platform/opencti-graphql/src/domain/container.js index 9e95e900e45a..f805ff9ca64f 100644 --- a/opencti-platform/opencti-graphql/src/domain/container.js +++ b/opencti-platform/opencti-graphql/src/domain/container.js @@ -374,8 +374,13 @@ export const aiSummary = async (context, user, args) => { # Reports ${JSON.stringify(content)} - # Instructions - - Your response should match the provided content format which is HTML, including appropriate HTML tags such as

,
    , and any necessary styling or structure. Ensure the content is well-formatted, semantic, and compatible with modern browsers. + # IMPORTANT: Output Format + - Your response MUST be in HTML format only. DO NOT use Markdown syntax. + - Use HTML tags like

    ,

    ,

    ,

      ,
    • , , for formatting. + - DO NOT use Markdown syntax such as ##, ###, **, *, - for lists, etc. + - Example: Use

      Key Findings

      NOT ## Key Findings + - Example: Use
      • Item
      NOT - Item + - Your response should include appropriate HTML tags such as

      ,
        , and any necessary styling or structure. Ensure the content is well-formatted, semantic, and compatible with modern browsers. - Do not include , and tags. `; const userPromptTopics = ` diff --git a/opencti-platform/opencti-graphql/src/domain/feed.ts b/opencti-platform/opencti-graphql/src/domain/feed.ts index 2d94ed53cdf9..63af3eaa4285 100644 --- a/opencti-platform/opencti-graphql/src/domain/feed.ts +++ b/opencti-platform/opencti-graphql/src/domain/feed.ts @@ -4,16 +4,15 @@ import { createEntity, deleteElementById } from '../database/middleware'; import { pageEntitiesConnection, storeLoadById } from '../database/middleware-loader'; import type { AuthContext, AuthUser } from '../types/user'; import type { FeedAddInput, MemberAccessInput, QueryFeedsArgs } from '../generated/graphql'; +import { FilterMode } from '../generated/graphql'; import type { BasicStoreEntityFeed } from '../types/store'; import { elReplace } from '../database/engine'; -import { INDEX_INTERNAL_OBJECTS } from '../database/utils'; import { FunctionalError, UnsupportedError, ValidationError } from '../config/errors'; import { isStixCyberObservable } from '../schema/stixCyberObservable'; import { isStixDomainObject } from '../schema/stixDomainObject'; import type { DomainFindById } from './domainTypes'; import { publishUserAction } from '../listener/UserActionListener'; import { isUserHasCapability, SYSTEM_USER, TAXIIAPI_SETCOLLECTIONS } from '../utils/access'; -import { FilterMode } from '../generated/graphql'; import { TAXIIAPI } from './user'; const checkFeedIntegrity = (input: FeedAddInput) => { @@ -71,7 +70,7 @@ export const editFeed = async (context: AuthContext, user: AuthUser, id: string, finalInput = { ...finalInput, restricted_members: finalInput.authorized_members } as FeedAddInput & { restricted_members: MemberAccessInput[] }; delete finalInput.authorized_members; } - await elReplace(INDEX_INTERNAL_OBJECTS, id, { doc: finalInput }); + await elReplace(feed._index, feed.internal_id, { doc: finalInput }); await publishUserAction({ user, event_type: 'mutation', diff --git a/opencti-platform/opencti-graphql/src/domain/group.js b/opencti-platform/opencti-graphql/src/domain/group.js index 07fde01c10de..9fc583fd520a 100644 --- a/opencti-platform/opencti-graphql/src/domain/group.js +++ b/opencti-platform/opencti-graphql/src/domain/group.js @@ -152,6 +152,7 @@ export const groupDelete = async (context, user, groupId) => { return notify(BUS_TOPICS[ENTITY_TYPE_GROUP].DELETE_TOPIC, group, user).then(() => groupId); }; +const groupAttributesUserCacheNoReset = ['name', 'description']; export const groupEditField = async (context, user, groupId, input) => { const { element } = await updateAttribute(context, user, groupId, ENTITY_TYPE_GROUP, input); await publishUserAction({ @@ -162,8 +163,8 @@ export const groupEditField = async (context, user, groupId, input) => { message: `updates \`${input.map((i) => i.key).join(', ')}\` for group \`${element.name}\``, context_data: { id: groupId, entity_type: ENTITY_TYPE_GROUP, input }, }); - // on editing the group confidence level, all members might have changed their effective level - if (input.find((i) => ['group_confidence_level', 'max_shareable_markings', 'restrict_delete'].includes(i.key))) { + // On editing the group, refresh the cache (only when really needed) + if (input.find((i) => !groupAttributesUserCacheNoReset.includes(i.key))) { await groupUsersCacheRefresh(context, user, groupId); } return notify(BUS_TOPICS[ENTITY_TYPE_GROUP].EDIT_TOPIC, element, user); diff --git a/opencti-platform/opencti-graphql/src/domain/rules.ts b/opencti-platform/opencti-graphql/src/domain/rules.ts index 516e768bea50..7906642869e8 100644 --- a/opencti-platform/opencti-graphql/src/domain/rules.ts +++ b/opencti-platform/opencti-graphql/src/domain/rules.ts @@ -2,9 +2,13 @@ import type { RuleRuntime } from '../types/rules'; import type { BasicRuleEntity } from '../types/store'; import { ENTITY_TYPE_RULE, ENTITY_TYPE_SETTINGS } from '../schema/internalObject'; import AttributedToAttributedRule from '../rules/attributed-to-attributed/AttributedToAttributedRule'; +import AttributionIndicatorIndicatesRule from '../rules/attribution-indicator-indicates/AttributionIndicatorIndicatesRule'; +import AttributionObservableRelatedRule from '../rules/attribution-observable-related/AttributionObservableRelatedRule'; import AttributionTargetsRule from '../rules/attribution-targets/AttributionTargetsRule'; import AttributionUseRule from '../rules/attribution-use/AttributionUseRule'; +import BelongsToAttributedRule from '../rules/belongs-to-attributed/BelongsToAttributedRule'; import RuleLocalizationOfTargets from '../rules/localization-of-targets/LocalizationOfTargetsRule'; +import InfrastructureObservableRelatedRule from '../rules/infrastructure-observable-related/InfrastructureObservableRelatedRule'; import LocatedAtLocatedRule from '../rules/located-at-located/LocatedAtLocatedRule'; import LocationTargetsRule from '../rules/location-targets/LocationTargetsRule'; import ParticipateToParts from '../rules/participate-to-parts/ParticipateToPartsRule'; @@ -20,6 +24,7 @@ import SightingIndicatorRule from '../rules/sighting-indicator/SightingIndicator import ReportRefIdentityPartOfRule from '../rules/report-refs-identity-part-of/ReportRefIdentityPartOfRule'; import ReportRefsIndicatorBasedOnRule from '../rules/report-refs-indicator-based-on/ReportRefIndicatorBasedOnRule'; import ReportRefObservableBasedOnRule from '../rules/report-refs-observable-based-on/ReportRefObservableBasedOnRule'; +import ReportRefObservableBelongsToRule from '../rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToRule'; import ReportRefsLocationLocatedAtRule from '../rules/report-refs-location-located-at/ReportRefLocationLocatedAtRule'; import ParentTechniqueUseRule from '../rules/parent-technique-use/ParentTechniqueUseRule'; import { BUS_TOPICS, DEV_MODE, logApp } from '../config/conf'; @@ -35,9 +40,13 @@ import { publishUserAction } from '../listener/UserActionListener'; export const RULES_DECLARATION: Array = [ AttributedToAttributedRule, + AttributionIndicatorIndicatesRule, + AttributionObservableRelatedRule, AttributionTargetsRule, - IndicateSightedRule, AttributionUseRule, + BelongsToAttributedRule, + IndicateSightedRule, + InfrastructureObservableRelatedRule, RuleLocalizationOfTargets, LocatedAtLocatedRule, LocationTargetsRule, @@ -52,6 +61,7 @@ export const RULES_DECLARATION: Array = [ ReportRefIdentityPartOfRule, ReportRefsIndicatorBasedOnRule, ReportRefObservableBasedOnRule, + ReportRefObservableBelongsToRule, ReportRefsLocationLocatedAtRule, ParentTechniqueUseRule, ]; diff --git a/opencti-platform/opencti-graphql/src/domain/stixCyberObservable.js b/opencti-platform/opencti-graphql/src/domain/stixCyberObservable.js index a2fe55a8e3b9..af8ed98dfbab 100644 --- a/opencti-platform/opencti-graphql/src/domain/stixCyberObservable.js +++ b/opencti-platform/opencti-graphql/src/domain/stixCyberObservable.js @@ -201,6 +201,7 @@ export const addStixCyberObservable = async (context, user, input) => { createIndicator, payload_bin, url, + upsertOperations, } = input; const graphQLType = type.replace(/(?:^|-|_)(\w)/g, (matches, letter) => letter.toUpperCase()); if (!input[graphQLType]) { @@ -209,7 +210,6 @@ export const addStixCyberObservable = async (context, user, input) => { checkScore(x_opencti_score); const lowerCaseTypes = ['Domain-Name', 'Email-Addr']; if (lowerCaseTypes.includes(type) && input[graphQLType].value) { - // eslint-disable-next-line no-param-reassign input[graphQLType].value = input[graphQLType].value.toLowerCase(); } if (type === 'Artifact' && input[graphQLType].file && isEmptyField(payload_bin)) { @@ -225,6 +225,7 @@ export const addStixCyberObservable = async (context, user, input) => { objectLabel, externalReferences, update, + upsertOperations, ...input[graphQLType], }; if (internal_id) { diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index d90be0b97ca3..a391b6adcdfa 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -1,5 +1,4 @@ import bcrypt from 'bcryptjs'; -import { authenticator } from 'otplib'; import * as R from 'ramda'; import { uniq } from 'ramda'; import { v4 as uuid } from 'uuid'; @@ -30,7 +29,7 @@ import { storeLoadById, } from '../database/middleware-loader'; import { delEditContext, notify, setEditContext } from '../database/redis'; -import { killUserSessions } from '../database/session'; +import { findUserSessions, killSessions, killUserSessions } from '../database/session'; import { buildPagination, isEmptyField, isNotEmptyField, READ_INDEX_INTERNAL_OBJECTS, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_RELATIONSHIPS_INDICES } from '../database/utils'; import { extractEntityRepresentativeName } from '../database/entity-representative'; import { publishUserAction } from '../listener/UserActionListener'; @@ -51,6 +50,7 @@ import { ENTITY_TYPE_IDENTITY_INDIVIDUAL } from '../schema/stixDomainObject'; import { ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; import { applyOrganizationRestriction, + buildUserOrganizationRestrictedFiltersOptions, BYPASS, CAPABILITIES_IN_DRAFT_NAMES, executionContext, @@ -73,7 +73,7 @@ import { defaultMarkingDefinitionsFromGroups, findGroupPaginated as findGroups } import { addIndividual } from './individual'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../modules/organization/organization-types'; import { ENTITY_TYPE_WORKSPACE } from '../modules/workspace/workspace-types'; -import { addFilter, extractFilterKeys, isFilterGroupNotEmpty } from '../utils/filtering/filtering-utils'; +import { addFilter, extractFilterKeys } from '../utils/filtering/filtering-utils'; import { testFilterGroup, testStringFilter } from '../utils/filtering/boolean-logic-engine'; import { computeUserEffectiveConfidenceLevel } from '../utils/confidence-level'; import { STATIC_NOTIFIER_EMAIL, STATIC_NOTIFIER_UI } from '../modules/notifier/notifier-statics'; @@ -88,6 +88,7 @@ import { ENTITY_TYPE_EMAIL_TEMPLATE } from '../modules/emailTemplate/emailTempla import { doYield } from '../utils/eventloop-utils'; import { sanitizeUser } from '../utils/templateContextSanitizer'; import { safeRender } from '../utils/safeEjs.client'; +import { totp } from '../utils/totp'; const BEARER = 'Bearer '; const BASIC = 'Basic '; @@ -184,7 +185,7 @@ const extractTokenFromBasicAuth = async (authorization) => { export const findById = async (context, user, userId) => { if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES) && user.id !== userId) { - // if no organization in common with the logged user + // if no organization in common with the logged user administrated organizations const memberOrganizations = await fullEntitiesThroughRelationsToList(context, user, userId, RELATION_PARTICIPATE_TO, ENTITY_TYPE_IDENTITY_ORGANIZATION); const myOrganizationsIds = user.administrated_organizations.map((organization) => organization.id); if (!memberOrganizations.map((organization) => organization.id).find((orgaId) => myOrganizationsIds.includes(orgaId))) { @@ -199,43 +200,14 @@ export const findById = async (context, user, userId) => { return buildCompleteUser(context, withoutPassword); }; -const buildUserOrganizationRestrictedFilters = (user, filters) => { - if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES)) { - // If user is not a set access administrator, user can only see attached organization users - const organizationIds = user.administrated_organizations.map((organization) => organization.id); - return { - mode: 'and', - filters: [ - { - key: 'regardingOf', - operator: 'eq', - values: [ - { - key: 'relationship_type', - values: ['participate-to'], - }, - { - key: 'id', - values: organizationIds, - }, - ], - mode: 'or', - }, - ], - filterGroups: filters && isFilterGroupNotEmpty(filters) ? [filters] : [], - }; - } - return filters; -}; - export const findAllUser = async (context, user, args) => { - const filters = buildUserOrganizationRestrictedFilters(user, args.filters); - return fullEntitiesList(context, user, [ENTITY_TYPE_USER], { ...args, filters }); + const { filters, noRegardingOfFilterIdsCheck } = buildUserOrganizationRestrictedFiltersOptions(user, args.filters); + return fullEntitiesList(context, user, [ENTITY_TYPE_USER], { ...args, filters, noRegardingOfFilterIdsCheck }); }; export const findUserPaginated = async (context, user, args) => { - const filters = buildUserOrganizationRestrictedFilters(user, args.filters); - return pageEntitiesConnection(context, user, [ENTITY_TYPE_USER], { ...args, filters }); + const { filters, noRegardingOfFilterIdsCheck } = buildUserOrganizationRestrictedFiltersOptions(user, args.filters); + return pageEntitiesConnection(context, user, [ENTITY_TYPE_USER], { ...args, filters, noRegardingOfFilterIdsCheck }); }; export const findCreators = (context, user, args) => { @@ -525,6 +497,25 @@ const isUserAdministratingOrga = (user, organizationId) => { return user.administrated_organizations.some(({ id }) => id === organizationId); }; +const loadUserToUpdateWithAccessCheck = async (context, user, userId) => { + const userToUpdate = await internalLoadById(context, user, userId, { type: ENTITY_TYPE_USER }); + if (!userToUpdate) { + throw FunctionalError(`${ENTITY_TYPE_USER} cannot be found.`, { userId }); + } + if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES) && user.id !== userId) { + // Check in an organization admin edits a user that's not in its administrated organizations + if (isOnlyOrgaAdmin(user)) { + const myAdministratedOrganizationsIds = user.administrated_organizations.map((orga) => orga.id); + if (!userToUpdate[RELATION_PARTICIPATE_TO]?.find((orga) => myAdministratedOrganizationsIds.includes(orga))) { + throw ForbiddenAccess(); + } + } else { + throw ForbiddenAccess(); + } + } + return userToUpdate; +}; + export const assignOrganizationToUser = async (context, user, userId, organizationId) => { if (isOnlyOrgaAdmin(user)) { // When user is organization admin, we make sure she is also admin of organization added @@ -532,10 +523,9 @@ export const assignOrganizationToUser = async (context, user, userId, organizati throw ForbiddenAccess(); } } - const targetUser = await findById(context, user, userId); - if (!targetUser) { - throw FunctionalError('Cannot add the relation, User cannot be found.', { userId }); - } + // check the user is accessible + const targetUser = await loadUserToUpdateWithAccessCheck(context, user, userId); + const input = { fromId: userId, toId: organizationId, relationship_type: RELATION_PARTICIPATE_TO }; const created = await createRelation(context, user, input); const actionEmail = ENABLED_DEMO_MODE ? REDACTED_USER.user_email : created.from.user_email; @@ -899,14 +889,7 @@ export const roleDeleteRelation = async (context, user, roleId, toId, relationsh // User related export const userEditField = async (context, user, userId, rawInputs) => { const inputs = []; - const userToUpdate = await internalLoadById(context, user, userId); - // Check in an organization admin edits a user that's not in its administrated organizations - const myAdministratedOrganizationsIds = user.administrated_organizations.map((orga) => orga.id); - if (isOnlyOrgaAdmin(user)) { - if (userId !== user.id && !userToUpdate[RELATION_PARTICIPATE_TO].find((orga) => myAdministratedOrganizationsIds.includes(orga))) { - throw ForbiddenAccess(); - } - } + const userToUpdate = await loadUserToUpdateWithAccessCheck(context, user, userId); let skipThisInput = false; for (let index = 0; index < rawInputs.length; index += 1) { const input = rawInputs[index]; @@ -1186,14 +1169,9 @@ export const deleteAllNotificationByUser = async (userId) => { * @returns {Promise<*>} */ export const userDelete = async (context, user, userId) => { - if (isOnlyOrgaAdmin(user)) { - // When user is organization admin, we make sure that the deleted user is in one of the administrated organizations of the admin - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - const myAdministratedOrganizationsIds = user.administrated_organizations.map(({ id }) => id); - if (!userData[RELATION_PARTICIPATE_TO].find((orga) => myAdministratedOrganizationsIds.includes(orga))) { - throw ForbiddenAccess(); - } - } + // check rights + await loadUserToUpdateWithAccessCheck(context, user, userId); + await deleteAllTriggerAndDigestByUser(userId); await deleteAllNotificationByUser(userId); await deleteAllWorkspaceForUser(context, user, userId); @@ -1213,14 +1191,14 @@ export const userDelete = async (context, user, userId) => { }; export const userAddRelation = async (context, user, userId, input) => { - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - if (!userData) { - throw FunctionalError(`Cannot add the relation, ${ENTITY_TYPE_USER} cannot be found.`, { userId }); - } + // check the user is accessible + const userData = await loadUserToUpdateWithAccessCheck(context, user, userId); + + // check the relationship type if (!isInternalRelationship(input.relationship_type)) { throw FunctionalError(`Only ${ABSTRACT_INTERNAL_RELATIONSHIP} can be added through this method, got ${input.relationship_type}.`); } - // Check in case organization admins adds non-grantable goup a user + // Check in case organization admins adds non-grantable group a user const myGrantableGroups = R.uniq(user.administrated_organizations.map((orga) => orga.grantable_groups).flat()); if (isOnlyOrgaAdmin(user)) { if (input.relationship_type === 'member-of' && !myGrantableGroups.includes(input.toId)) { @@ -1260,10 +1238,9 @@ export const userDeleteRelation = async (context, user, targetUser, toId, relati }; export const userIdDeleteRelation = async (context, user, userId, toId, relationshipType) => { - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - if (!userData) { - throw FunctionalError('Cannot delete the relation, User cannot be found.', { userId }); - } + // check the user is accessible + const userData = await loadUserToUpdateWithAccessCheck(context, user, userId); + if (!isInternalRelationship(relationshipType)) { throw FunctionalError(`Only ${ABSTRACT_INTERNAL_RELATIONSHIP} can be deleted through this method, got ${relationshipType}.`); } @@ -1277,10 +1254,8 @@ export const userDeleteOrganizationRelation = async (context, user, userId, toId throw ForbiddenAccess(); } } - const targetUser = await findById(context, user, userId); - if (!targetUser) { - throw FunctionalError('Cannot delete the relation, User cannot be found.', { userId }); - } + // check the user is accessible + const targetUser = await loadUserToUpdateWithAccessCheck(context, user, userId); const { to } = await deleteRelationsByFromAndTo(context, user, userId, toId, RELATION_PARTICIPATE_TO, ABSTRACT_INTERNAL_RELATIONSHIP); if (to.authorized_authorities?.includes(userId)) { @@ -1408,8 +1383,8 @@ export const login = async (email, password) => { }; export const otpUserGeneration = (user) => { - const secret = authenticator.generateSecret(); - const uri = authenticator.keyuri(user.user_email, 'OpenCTI', secret); + const secret = totp.generateSecret(); + const uri = totp.generateURI({ label: user.user_email, issuer: 'OpenCTI', secret }); return { secret, uri }; }; @@ -1437,12 +1412,12 @@ export const otpUserActivation = async (context, user, { secret, code }) => { if (user.otp_activated) { throw UnsupportedError('You need to deactivate your current 2FA before generating a new one'); } - const isValidated = authenticator.check(code, secret); - if (isValidated) { - const uri = authenticator.keyuri(user.user_email, 'OpenCTI', secret); + const { valid } = await totp.verify({ secret, token: code }); + if (valid) { + const uri = totp.generateURI({ label: user.user_email, issuer: 'OpenCTI', secret }); const patch = { otp_activated: true, otp_secret: secret, otp_qr: uri }; const { element } = await patchAttribute(context, user, user.id, ENTITY_TYPE_USER, patch); - context.req.session.user.otp_validated = isValidated; + context.req.session.user.otp_validated = valid; return notify(BUS_TOPICS[ENTITY_TYPE_USER].EDIT_TOPIC, element, user); } throw AuthenticationFailure(); @@ -1461,13 +1436,13 @@ export const otpUserLogin = async (req, user, { code }) => { if (!user.otp_activated) { throw AuthenticationFailure(); } - const isValidated = authenticator.check(code, user.otp_secret); - if (!isValidated) { + const { valid } = await totp.verify({ secret: user.otp_secret, token: code }); + if (!valid) { throw AuthenticationFailure(); } - req.session.user.otp_validated = isValidated; + req.session.user.otp_validated = valid; req.session.save(); - return isValidated; + return valid; }; const virtualOrganizationAdminCapability = { @@ -1690,10 +1665,9 @@ export const userRenewToken = async (context, user, userId) => { throw FunctionalError('Cannot renew token of admin user defined in configuration, please change configuration instead.'); } - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - if (!userData) { - throw FunctionalError(`Cannot renew token, ${userId} user cannot be found.`); - } + // check the user is accessible + const userData = await loadUserToUpdateWithAccessCheck(context, user, userId); + const patch = { api_token: uuid() }; const { element } = await patchAttribute(context, user, userId, ENTITY_TYPE_USER, patch); @@ -1732,6 +1706,27 @@ const validateUser = (user, settings) => { } }; +export const enforceSessionLimit = async (user, settings) => { + if (settings.platform_session_max_concurrent && settings.platform_session_max_concurrent > 0) { + const sessions = await findUserSessions(user.id); + if (sessions.length >= settings.platform_session_max_concurrent) { + const sortedSessions = sessions.sort((a, b) => { + if (a.created < b.created) { + return -1; + } + if (a.created > b.created) { + return 1; + } + return 0; + }); + const sessionsToKill = sortedSessions.slice(0, sessions.length - settings.platform_session_max_concurrent + 1); + await killSessions(sessionsToKill.map((s) => s.id)); + return sessionsToKill.length; + } + } + return 0; +}; + export const sessionAuthenticateUser = async (context, req, user, provider) => { let platformUsers = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_USER); let logged = platformUsers.get(user.internal_id); @@ -1745,17 +1740,20 @@ export const sessionAuthenticateUser = async (context, req, user, provider) => { } const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS); validateUser(logged, settings); + const withOrigin = userWithOrigin(req, logged); + const numberOfKilledSessions = await enforceSessionLimit(withOrigin, settings); // Build and save the session req.session.user = { id: user.id, session_creation: now(), otp_validated: false }; req.session.session_provider = provider; req.session.save(); // Publish the login event - const userOrigin = userWithOrigin(req, logged); + const userOrigin = withOrigin; await publishUserAction({ user: userOrigin, event_type: 'authentication', event_access: 'administration', event_scope: 'login', + session_kill: numberOfKilledSessions, context_data: { provider }, }); return userOrigin; @@ -1848,11 +1846,13 @@ export const findDefaultDashboards = async (context, user, currentUser) => { // region context export const userCleanContext = async (context, user, userId) => { + await loadUserToUpdateWithAccessCheck(context, user, userId); await delEditContext(user, userId); return storeLoadById(context, user, userId, ENTITY_TYPE_USER); }; export const userEditContext = async (context, user, userId, input) => { + await loadUserToUpdateWithAccessCheck(context, user, userId); await setEditContext(user, userId, input); return storeLoadById(context, user, userId, ENTITY_TYPE_USER); }; diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index c822ffc42359..8e8300249647 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -134,7 +134,7 @@ export type AdministrativeArea = BasicObject & Location & StixCoreObject & StixD observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -316,6 +316,9 @@ export type AdministrativeAreaAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -327,6 +330,7 @@ export type AdministrativeAreaAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -477,7 +481,7 @@ export type Artifact = BasicObject & HashedObservable & StixCoreObject & StixCyb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; payload_bin?: Maybe; pendingFiles?: Maybe; refreshed_at?: Maybe; @@ -653,9 +657,13 @@ export type ArtifactAddInput = { decryption_key?: InputMaybe; encryption_algorithm?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; hashes?: InputMaybe>>; mime_type?: InputMaybe; payload_bin?: InputMaybe; + upsertOperations?: InputMaybe>; url?: InputMaybe; x_opencti_additional_names?: InputMaybe>>; }; @@ -726,7 +734,7 @@ export type AttackPattern = BasicObject & StixCoreObject & StixDomainObject & St opinions?: Maybe; opinions_metrics?: Maybe; parentAttackPatterns?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -942,6 +950,9 @@ export type AttackPatternAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; killChainPhases?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; @@ -952,6 +963,7 @@ export type AttackPatternAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_mitre_detection?: InputMaybe; x_mitre_id?: InputMaybe; x_mitre_permissions_required?: InputMaybe>>; @@ -1181,7 +1193,7 @@ export type AutonomousSystem = BasicObject & StixCoreObject & StixCyberObservabl observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -1353,9 +1365,13 @@ export type AutonomousSystemToStixArgs = { export type AutonomousSystemAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; name?: InputMaybe; number: Scalars['Int']['input']; rir?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type BackgroundTask = { @@ -1512,7 +1528,7 @@ export type BankAccount = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -1686,14 +1702,18 @@ export type BankAccountAddInput = { account_number?: InputMaybe; bic?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; iban?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type BasicObject = { entity_type: Scalars['String']['output']; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -1704,7 +1724,7 @@ export type BasicRelationship = { fromRole?: Maybe; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; toRole?: Maybe; @@ -1771,7 +1791,7 @@ export type Campaign = BasicObject & StixCoreObject & StixDomainObject & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -1953,6 +1973,9 @@ export type CampaignAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; lang?: InputMaybe; last_seen?: InputMaybe; @@ -1966,6 +1989,7 @@ export type CampaignAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -2093,7 +2117,7 @@ export type Capability = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -2155,7 +2179,7 @@ export type Case = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -2421,7 +2445,7 @@ export type CaseIncident = BasicObject & Case & Container & StixCoreObject & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; priority?: Maybe; @@ -2647,6 +2671,9 @@ export type CaseIncidentAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -2662,6 +2689,7 @@ export type CaseIncidentAddInput = { severity?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -2743,7 +2771,7 @@ export type CaseRfi = BasicObject & Case & Container & StixCoreObject & StixDoma observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; priority?: Maybe; @@ -2969,6 +2997,9 @@ export type CaseRfiAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; information_types?: InputMaybe>; lang?: InputMaybe; modified?: InputMaybe; @@ -2984,6 +3015,7 @@ export type CaseRfiAddInput = { severity?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_request_access?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -3064,7 +3096,7 @@ export type CaseRft = BasicObject & Case & Container & StixCoreObject & StixDoma observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; priority?: Maybe; @@ -3288,6 +3320,9 @@ export type CaseRftAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -3303,6 +3338,7 @@ export type CaseRftAddInput = { stix_id?: InputMaybe; takedown_types?: InputMaybe>; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -3347,7 +3383,7 @@ export type CaseTemplate = BasicObject & InternalObject & { metrics?: Maybe>>; modified?: Maybe; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; tasks: TaskTemplateConnection; }; @@ -3476,7 +3512,7 @@ export type Channel = BasicObject & StixCoreObject & StixDomainObject & StixObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -3658,6 +3694,9 @@ export type ChannelAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -3667,6 +3706,7 @@ export type ChannelAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -3757,7 +3797,7 @@ export type City = BasicObject & Location & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -3939,6 +3979,9 @@ export type CityAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -3951,6 +3994,7 @@ export type CityAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -4082,7 +4126,7 @@ export type Connector = BasicObject & InternalObject & { metrics?: Maybe>>; name: Scalars['String']['output']; only_contextual?: Maybe; - parent_types: Array>; + parent_types: Array; playbook_compatible?: Maybe; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; @@ -4107,6 +4151,7 @@ export type ConnectorConfig = { push: Scalars['String']['output']; push_exchange: Scalars['String']['output']; push_routing: Scalars['String']['output']; + s3: S3Connection; }; export type ConnectorConfiguration = { @@ -4164,7 +4209,7 @@ export type ConnectorManager = BasicObject & InternalObject & { last_sync_execution?: Maybe; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; public_key: Scalars['String']['output']; standard_id: Scalars['String']['output']; }; @@ -4238,7 +4283,7 @@ export type Container = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relatedContainers?: Maybe; reports?: Maybe; @@ -4544,7 +4589,7 @@ export type Country = BasicObject & Location & StixCoreObject & StixDomainObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -4727,6 +4772,9 @@ export type CountryAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -4738,6 +4786,7 @@ export type CountryAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -4827,7 +4876,7 @@ export type CourseOfAction = BasicObject & StixCoreObject & StixDomainObject & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -5011,6 +5060,9 @@ export type CourseOfActionAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -5020,6 +5072,7 @@ export type CourseOfActionAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_mitre_id?: InputMaybe; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; @@ -5140,7 +5193,7 @@ export type Credential = BasicObject & StixCoreObject & StixCyberObservable & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -5316,6 +5369,10 @@ export type CredentialToStixArgs = { export type CredentialAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -5349,7 +5406,7 @@ export type CryptocurrencyWallet = BasicObject & StixCoreObject & StixCyberObser observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -5521,6 +5578,10 @@ export type CryptocurrencyWalletToStixArgs = { export type CryptocurrencyWalletAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -5554,7 +5615,7 @@ export type CryptographicKey = BasicObject & StixCoreObject & StixCyberObservabl observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -5726,6 +5787,10 @@ export type CryptographicKeyToStixArgs = { export type CryptographicKeyAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -5919,7 +5984,7 @@ export type DataComponent = BasicObject & StixCoreObject & StixDomainObject & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -6101,6 +6166,9 @@ export type DataComponentAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -6110,6 +6178,7 @@ export type DataComponentAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -6178,7 +6247,7 @@ export type DataSource = BasicObject & StixCoreObject & StixDomainObject & StixO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -6362,6 +6431,9 @@ export type DataSourceAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -6371,6 +6443,7 @@ export type DataSourceAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_mitre_platforms?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -6420,7 +6493,7 @@ export type DecayExclusionRule = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -6480,7 +6553,7 @@ export type DecayRule = BasicObject & InternalObject & { metrics?: Maybe>>; name: Scalars['String']['output']; order: Scalars['Int']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -6648,7 +6721,7 @@ export type Directory = BasicObject & StixCoreObject & StixCyberObservable & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; path: Scalars['String']['output']; path_enc?: Maybe; pendingFiles?: Maybe; @@ -6823,9 +6896,13 @@ export type DirectoryAddInput = { atime?: InputMaybe; ctime?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; mtime?: InputMaybe; path: Scalars['String']['input']; path_enc?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type Display = { @@ -6855,7 +6932,7 @@ export type DisseminationList = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -6935,7 +7012,7 @@ export type DomainName = BasicObject & StixCoreObject & StixCyberObservable & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -7107,6 +7184,10 @@ export type DomainNameToStixArgs = { export type DomainNameAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value: Scalars['String']['input']; }; @@ -7287,7 +7368,7 @@ export type EmailAddr = BasicObject & StixCoreObject & StixCyberObservable & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -7460,6 +7541,10 @@ export type EmailAddrToStixArgs = { export type EmailAddrAddInput = { display_name?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -7498,7 +7583,7 @@ export type EmailMessage = BasicObject & StixCoreObject & StixCyberObservable & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; received_lines?: Maybe>>; refreshed_at?: Maybe; @@ -7674,10 +7759,14 @@ export type EmailMessageAddInput = { body?: InputMaybe; content_type?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; is_multipart?: InputMaybe; message_id?: InputMaybe; received_lines?: InputMaybe>>; subject?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type EmailMimePartType = BasicObject & StixCoreObject & StixCyberObservable & StixObject & { @@ -7713,7 +7802,7 @@ export type EmailMimePartType = BasicObject & StixCoreObject & StixCyberObservab observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -7887,6 +7976,10 @@ export type EmailMimePartTypeAddInput = { content_disposition?: InputMaybe; content_type?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; }; export type EmailTemplate = BasicObject & InternalObject & { @@ -7897,7 +7990,7 @@ export type EmailTemplate = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; sender_email: Scalars['String']['output']; standard_id: Scalars['String']['output']; template_body: Scalars['String']['output']; @@ -8018,7 +8111,7 @@ export type Event = BasicObject & StixCoreObject & StixDomainObject & StixObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -8202,6 +8295,9 @@ export type EventAddInput = { event_types?: InputMaybe>; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -8212,6 +8308,7 @@ export type EventAddInput = { stix_id?: InputMaybe; stop_time?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -8257,7 +8354,7 @@ export type ExclusionList = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -8333,7 +8430,7 @@ export type ExternalReference = BasicObject & StixMetaObject & StixObject & { jobs?: Maybe>>; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles: FileConnection; references?: Maybe; refreshed_at?: Maybe; @@ -8395,6 +8492,9 @@ export type ExternalReferenceAddInput = { description?: InputMaybe; external_id?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; hash?: InputMaybe; modified?: InputMaybe; source_name: Scalars['String']['input']; @@ -8600,7 +8700,7 @@ export type Feedback = BasicObject & Case & Container & StixCoreObject & StixDom observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; rating?: Maybe; @@ -8820,6 +8920,9 @@ export type FeedbackAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -8832,6 +8935,7 @@ export type FeedbackAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -9003,7 +9107,7 @@ export type FintelDesign = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; textColor?: Maybe; }; @@ -9044,7 +9148,7 @@ export type FintelTemplate = BasicObject & InternalObject & { instance_filters?: Maybe; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; settings_types: Array; standard_id: Scalars['String']['output']; start_date?: Maybe; @@ -9147,6 +9251,7 @@ export enum Format { } export enum FormsOrdering { + Score = '_score', Active = 'active', CreatedAt = 'created_at', Name = 'name', @@ -9179,7 +9284,7 @@ export type Group = BasicObject & InternalObject & { name: Scalars['String']['output']; no_creators?: Maybe; not_shareable_marking_types: Array; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; restrict_delete?: Maybe; roles?: Maybe; @@ -9309,7 +9414,7 @@ export type Grouping = BasicObject & Container & StixCoreObject & StixDomainObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -9531,6 +9636,9 @@ export type GroupingAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -9541,6 +9649,7 @@ export type GroupingAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -9594,6 +9703,7 @@ export type Hash = { export type HashInput = { algorithm: Scalars['String']['input']; hash: Scalars['String']['input']; + upsertOperations?: InputMaybe>; }; export type HashedObservable = { @@ -9626,7 +9736,7 @@ export type HashedObservable = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -9836,7 +9946,7 @@ export type Hostname = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10008,6 +10118,10 @@ export type HostnameToStixArgs = { export type HostnameAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -10042,7 +10156,7 @@ export type IPv4Addr = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10215,7 +10329,11 @@ export type IPv4AddrToStixArgs = { export type IPv4AddrAddInput = { belongsTo?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; resolvesTo?: InputMaybe>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -10250,7 +10368,7 @@ export type IPv6Addr = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10422,6 +10540,10 @@ export type IPv6AddrToStixArgs = { export type IPv6AddrAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -10472,7 +10594,7 @@ export type Identity = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10648,6 +10770,10 @@ export type IdentityAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -10658,6 +10784,7 @@ export type IdentityAddInput = { stix_id?: InputMaybe; type: IdentityType; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -10769,7 +10896,7 @@ export type Incident = BasicObject & StixCoreObject & StixDomainObject & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -10953,6 +11080,9 @@ export type IncidentAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; incident_type?: InputMaybe; lang?: InputMaybe; @@ -10970,6 +11100,7 @@ export type IncidentAddInput = { source?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -11114,7 +11245,7 @@ export type Indicator = BasicObject & StixCoreObject & StixDomainObject & StixOb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pattern?: Maybe; pattern_type?: Maybe; pattern_version?: Maybe; @@ -11311,6 +11442,9 @@ export type IndicatorAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; indicator_types?: InputMaybe>; killChainPhases?: InputMaybe>; lang?: InputMaybe; @@ -11325,6 +11459,7 @@ export type IndicatorAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; valid_from?: InputMaybe; valid_until?: InputMaybe; x_mitre_platforms?: InputMaybe>; @@ -11434,7 +11569,7 @@ export type Individual = BasicObject & Identity & StixCoreObject & StixDomainObj opinions?: Maybe; opinions_metrics?: Maybe; organizations?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -11620,6 +11755,9 @@ export type IndividualAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -11630,6 +11768,7 @@ export type IndividualAddInput = { roles?: InputMaybe>>; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_firstname?: InputMaybe; x_opencti_lastname?: InputMaybe; @@ -11750,7 +11889,7 @@ export type Infrastructure = BasicObject & StixCoreObject & StixDomainObject & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -11931,6 +12070,9 @@ export type InfrastructureAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; infrastructure_types?: InputMaybe>>; killChainPhases?: InputMaybe>>; @@ -11944,6 +12086,7 @@ export type InfrastructureAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -12036,7 +12179,7 @@ export type IngestionCsv = BasicObject & InternalObject & { markings?: Maybe>; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; scheduling_period?: Maybe; standard_id: Scalars['String']['output']; @@ -12122,7 +12265,7 @@ export type IngestionJson = BasicObject & InternalObject & { pagination_with_sub_page?: Maybe; pagination_with_sub_page_attribute_path?: Maybe; pagination_with_sub_page_query_verb?: Maybe; - parent_types: Array>; + parent_types: Array; query_attributes?: Maybe>; refreshed_at?: Maybe; scheduling_period?: Maybe; @@ -12262,6 +12405,7 @@ export type IngestionTaxii = BasicObject & InternalObject & { parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; + toConfigurationExport: Scalars['String']['output']; updated_at?: Maybe; uri: Scalars['String']['output']; user?: Maybe; @@ -12269,17 +12413,24 @@ export type IngestionTaxii = BasicObject & InternalObject & { version: TaxiiVersion; }; +export type IngestionTaxiiAddAutoUserInput = { + confidence_level: Scalars['Int']['input']; + user_name: Scalars['String']['input']; +}; + export type IngestionTaxiiAddInput = { added_after_start?: InputMaybe; authentication_type: IngestionAuthType; authentication_value?: InputMaybe; + automatic_user?: InputMaybe; collection: Scalars['String']['input']; + confidence_level?: InputMaybe; confidence_to_score?: InputMaybe; description?: InputMaybe; ingestion_running?: InputMaybe; name: Scalars['String']['input']; uri: Scalars['String']['input']; - user_id?: InputMaybe; + user_id: Scalars['String']['input']; version: TaxiiVersion; }; @@ -12364,7 +12515,7 @@ export type InternalRelationship = BasicRelationship & { fromRole?: Maybe; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; to?: Maybe; @@ -12425,7 +12576,7 @@ export type IntrusionSet = BasicObject & StixCoreObject & StixDomainObject & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; primary_motivation?: Maybe; @@ -12610,6 +12761,9 @@ export type IntrusionSetAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; goals?: InputMaybe>>; lang?: InputMaybe; @@ -12626,6 +12780,7 @@ export type IntrusionSetAddInput = { secondary_motivations?: InputMaybe>>; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -12816,7 +12971,7 @@ export type KillChainPhase = BasicObject & StixMetaObject & StixObject & { kill_chain_name: Scalars['String']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; phase_name: Scalars['String']['output']; refreshed_at?: Maybe; representative: Representative; @@ -12909,7 +13064,7 @@ export type Label = BasicObject & StixMetaObject & StixObject & { is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -13016,7 +13171,7 @@ export type Language = BasicObject & StixCoreObject & StixDomainObject & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -13196,6 +13351,10 @@ export type LanguageAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -13303,7 +13462,7 @@ export type Location = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; precision?: Maybe; refreshed_at?: Maybe; @@ -13477,6 +13636,10 @@ export type LocationAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -13489,6 +13652,7 @@ export type LocationAddInput = { stix_id?: InputMaybe; type: Scalars['String']['input']; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -13636,7 +13800,7 @@ export type MacAddr = BasicObject & StixCoreObject & StixCyberObservable & StixO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -13808,6 +13972,10 @@ export type MacAddrToStixArgs = { export type MacAddrAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -13857,7 +14025,7 @@ export type Malware = BasicObject & StixCoreObject & StixDomainObject & StixObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -14041,6 +14209,9 @@ export type MalwareAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; implementation_languages?: InputMaybe>>; is_family?: InputMaybe; @@ -14058,6 +14229,7 @@ export type MalwareAddInput = { samples?: InputMaybe>>; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -14122,7 +14294,7 @@ export type MalwareAnalysis = BasicObject & StixCoreObject & StixDomainObject & operatingSystem?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; product: Scalars['String']['output']; @@ -14314,6 +14486,9 @@ export type MalwareAnalysisAddInput = { createdBy?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; hostVm?: InputMaybe; installedSoftware?: InputMaybe>>; lang?: InputMaybe; @@ -14331,6 +14506,7 @@ export type MalwareAnalysisAddInput = { stix_id?: InputMaybe; submitted?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; version?: InputMaybe; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -14429,7 +14605,7 @@ export type ManagedConnector = BasicObject & InternalObject & { manager_requested_status: Scalars['String']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -14497,7 +14673,7 @@ export type MarkingDefinition = BasicObject & StixMetaObject & StixObject & { is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -14700,7 +14876,7 @@ export type MediaContent = BasicObject & StixCoreObject & StixCyberObservable & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; publication_date?: Maybe; refreshed_at?: Maybe; @@ -14875,9 +15051,13 @@ export type MediaContentToStixArgs = { export type MediaContentAddInput = { content?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; media_category?: InputMaybe; publication_date?: InputMaybe; title?: InputMaybe; + upsertOperations?: InputMaybe>; url: Scalars['String']['input']; }; @@ -15162,6 +15342,7 @@ export type Mutation = { ingestionRssDelete?: Maybe; ingestionRssFieldPatch?: Maybe; ingestionTaxiiAdd?: Maybe; + ingestionTaxiiAddAutoUser?: Maybe; ingestionTaxiiCollectionAdd?: Maybe; ingestionTaxiiCollectionDelete?: Maybe; ingestionTaxiiCollectionFieldPatch?: Maybe; @@ -15286,6 +15467,7 @@ export type Mutation = { ruleManagerClean: RuleManager; ruleSetActivation: Rule; rulesRescan?: Maybe; + runMigration: Scalars['Boolean']['output']; savedFilterAdd?: Maybe; savedFilterDelete?: Maybe; savedFilterFieldPatch?: Maybe; @@ -15345,6 +15527,7 @@ export type Mutation = { supportPackageDelete?: Maybe; supportPackageForceZip?: Maybe; synchronizerAdd?: Maybe; + synchronizerAddAutoUser?: Maybe; synchronizerEdit?: Maybe; synchronizerStart?: Maybe; synchronizerStop?: Maybe; @@ -15573,6 +15756,7 @@ export type MutationAiVictimGenerateReportArgs = { export type MutationArtifactImportArgs = { createdBy?: InputMaybe; file: Scalars['Upload']['input']; + fileMarkings?: InputMaybe>>; objectLabel?: InputMaybe>>; objectMarking?: InputMaybe>>; x_opencti_description?: InputMaybe; @@ -16452,6 +16636,12 @@ export type MutationIngestionTaxiiAddArgs = { }; +export type MutationIngestionTaxiiAddAutoUserArgs = { + id: Scalars['ID']['input']; + input: IngestionTaxiiAddAutoUserInput; +}; + + export type MutationIngestionTaxiiCollectionAddArgs = { input: IngestionTaxiiCollectionAddInput; }; @@ -17127,6 +17317,11 @@ export type MutationRulesRescanArgs = { }; +export type MutationRunMigrationArgs = { + migrationName: Scalars['String']['input']; +}; + + export type MutationSavedFilterAddArgs = { input: SavedFilterAddInput; }; @@ -17386,6 +17581,7 @@ export type MutationStixCyberObservableAddArgs = { stix_id?: InputMaybe; type: Scalars['String']['input']; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_description?: InputMaybe; x_opencti_modified_at?: InputMaybe; x_opencti_score?: InputMaybe; @@ -17516,6 +17712,12 @@ export type MutationSynchronizerAddArgs = { }; +export type MutationSynchronizerAddAutoUserArgs = { + id: Scalars['ID']['input']; + input: SynchronizerAddAutoUserInput; +}; + + export type MutationSynchronizerEditArgs = { id: Scalars['ID']['input']; }; @@ -17948,7 +18150,7 @@ export type Mutex = BasicObject & StixCoreObject & StixCyberObservable & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -18119,7 +18321,11 @@ export type MutexToStixArgs = { export type MutexAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; name?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type NlqResponse = { @@ -18169,7 +18375,7 @@ export type Narrative = BasicObject & StixCoreObject & StixDomainObject & StixOb opinions?: Maybe; opinions_metrics?: Maybe; parentNarratives?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -18351,6 +18557,9 @@ export type NarrativeAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -18361,6 +18570,7 @@ export type NarrativeAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -18428,7 +18638,7 @@ export type NetworkTraffic = BasicObject & StixCoreObject & StixCyberObservable observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; protocols?: Maybe>>; refreshed_at?: Maybe; @@ -18609,6 +18819,9 @@ export type NetworkTrafficAddInput = { dst_port?: InputMaybe; end?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; is_active?: InputMaybe; networkDst?: InputMaybe; networkSrc?: InputMaybe; @@ -18617,6 +18830,7 @@ export type NetworkTrafficAddInput = { src_packets?: InputMaybe; src_port?: InputMaybe; start?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type Note = BasicObject & Container & StixCoreObject & StixDomainObject & StixObject & { @@ -18665,7 +18879,7 @@ export type Note = BasicObject & Container & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -18883,6 +19097,9 @@ export type NoteAddInput = { createdBy?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; likelihood?: InputMaybe; modified?: InputMaybe; @@ -18894,6 +19111,7 @@ export type NoteAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -18990,7 +19208,7 @@ export type Notification = BasicObject & InternalObject & { name: Scalars['String']['output']; notification_content: Array; notification_type: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at?: Maybe; @@ -19047,7 +19265,7 @@ export type Notifier = BasicObject & InternalObject & { notifier_configuration: Scalars['String']['output']; notifier_connector: NotifierConnector; notifier_connector_id: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -19165,7 +19383,7 @@ export type ObservedData = BasicObject & Container & StixCoreObject & StixDomain observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -19380,6 +19598,9 @@ export type ObservedDataAddInput = { createdBy?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_observed: Scalars['DateTime']['input']; lang?: InputMaybe; last_observed: Scalars['DateTime']['input']; @@ -19392,6 +19613,7 @@ export type ObservedDataAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -19511,7 +19733,7 @@ export type Opinion = BasicObject & Container & StixCoreObject & StixDomainObjec opinion: Scalars['String']['output']; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -19728,6 +19950,9 @@ export type OpinionAddInput = { explanation?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; objectLabel?: InputMaybe>>; @@ -19884,7 +20109,7 @@ export type Organization = BasicObject & Identity & StixCoreObject & StixDomainO opinions?: Maybe; opinions_metrics?: Maybe; parentOrganizations?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -20083,6 +20308,9 @@ export type OrganizationAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -20092,6 +20320,7 @@ export type OrganizationAddInput = { roles?: InputMaybe>>; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_organization_type?: InputMaybe; @@ -20221,7 +20450,7 @@ export type PaymentCard = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -20395,7 +20624,11 @@ export type PaymentCardAddInput = { cvv?: InputMaybe; expiration_date?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; holder_name?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type Persona = BasicObject & StixCoreObject & StixCyberObservable & StixObject & { @@ -20428,7 +20661,7 @@ export type Persona = BasicObject & StixCoreObject & StixCyberObservable & StixO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; persona_name: Scalars['String']['output']; persona_type: Scalars['String']['output']; @@ -20600,8 +20833,13 @@ export type PersonaToStixArgs = { }; export type PersonaAddInput = { + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; persona_name: Scalars['String']['input']; persona_type: Scalars['String']['input']; + upsertOperations?: InputMaybe>; }; export type PhoneNumber = BasicObject & StixCoreObject & StixCyberObservable & StixObject & { @@ -20634,7 +20872,7 @@ export type PhoneNumber = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -20806,6 +21044,10 @@ export type PhoneNumberToStixArgs = { export type PhoneNumberAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -20931,7 +21173,7 @@ export type PirRelationship = BasicRelationship & { fromType: Scalars['String']['output']; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; pir_explanation?: Maybe>; pir_score?: Maybe; refreshed_at?: Maybe; @@ -21187,7 +21429,7 @@ export type Position = BasicObject & Location & StixCoreObject & StixDomainObjec observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; postal_code?: Maybe; @@ -21371,6 +21613,9 @@ export type PositionAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -21384,6 +21629,7 @@ export type PositionAddInput = { stix_id?: InputMaybe; street_address?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -21495,7 +21741,7 @@ export type Process = BasicObject & StixCoreObject & StixCyberObservable & StixO opinions?: Maybe; opinions_metrics?: Maybe; owner_sid?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pid?: Maybe; priority?: Maybe; @@ -21683,6 +21929,9 @@ export type ProcessAddInput = { display_name?: InputMaybe; environment_variables?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; group_name?: InputMaybe; integrity_level?: InputMaybe; is_hidden?: InputMaybe; @@ -21695,6 +21944,7 @@ export type ProcessAddInput = { service_type?: InputMaybe; start_type?: InputMaybe; startup_info?: InputMaybe>>; + upsertOperations?: InputMaybe>; window_title?: InputMaybe; x_opencti_description?: InputMaybe; }; @@ -22143,6 +22393,7 @@ export type Query = { supportPackage?: Maybe; supportPackages?: Maybe; synchronizer?: Maybe; + synchronizerAddInputFromImport: SynchronizerAddInputFromImport; synchronizerFetch?: Maybe>>; synchronizers?: Maybe; system?: Maybe; @@ -22155,6 +22406,7 @@ export type Query = { tasks?: Maybe; taxiiCollection?: Maybe; taxiiCollections?: Maybe; + taxiiFeedAddInputFromImport: TaxiiFeedAddInputFromImport; theme?: Maybe; themes?: Maybe; threatActor?: Maybe; @@ -24867,6 +25119,11 @@ export type QuerySynchronizerArgs = { }; +export type QuerySynchronizerAddInputFromImportArgs = { + file: Scalars['Upload']['input']; +}; + + export type QuerySynchronizerFetchArgs = { input?: InputMaybe; }; @@ -24950,6 +25207,11 @@ export type QueryTaxiiCollectionsArgs = { }; +export type QueryTaxiiFeedAddInputFromImportArgs = { + file: Scalars['Upload']['input']; +}; + + export type QueryThemeArgs = { id: Scalars['ID']['input']; }; @@ -25300,7 +25562,7 @@ export type Region = BasicObject & Location & StixCoreObject & StixDomainObject opinions?: Maybe; opinions_metrics?: Maybe; parentRegions?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -25483,6 +25745,9 @@ export type RegionAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -25494,6 +25759,7 @@ export type RegionAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -25628,7 +25894,7 @@ export type Report = BasicObject & Container & StixCoreObject & StixDomainObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; published?: Maybe; @@ -25851,6 +26117,9 @@ export type ReportAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -25865,6 +26134,7 @@ export type ReportAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_reliability?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -26095,7 +26365,7 @@ export type Role = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -26200,6 +26470,17 @@ export type RuleTask = BackgroundTask & { work?: Maybe; }; +export type S3Connection = { + __typename?: 'S3Connection'; + access_key: Scalars['String']['output']; + bucket_name: Scalars['String']['output']; + bucket_region: Scalars['String']['output']; + endpoint: Scalars['String']['output']; + port: Scalars['Int']['output']; + secret_key: Scalars['String']['output']; + use_ssl: Scalars['Boolean']['output']; +}; + export type SshKey = BasicObject & StixCoreObject & StixCyberObservable & StixObject & { __typename?: 'SSHKey'; cases?: Maybe; @@ -26237,7 +26518,7 @@ export type SshKey = BasicObject & StixCoreObject & StixCyberObservable & StixOb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; public_key?: Maybe; refreshed_at?: Maybe; @@ -26411,11 +26692,16 @@ export type SshKeyAddInput = { comment?: InputMaybe; created?: InputMaybe; expiration_date?: InputMaybe; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; fingerprint_md5?: InputMaybe; fingerprint_sha256: Scalars['String']['input']; key_length?: InputMaybe; key_type?: InputMaybe; public_key?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type SavedFilter = BasicObject & InternalObject & { @@ -26425,7 +26711,7 @@ export type SavedFilter = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; scope: Scalars['String']['output']; standard_id: Scalars['String']['output']; }; @@ -26506,7 +26792,7 @@ export type Sector = BasicObject & Identity & StixCoreObject & StixDomainObject opinions?: Maybe; opinions_metrics?: Maybe; parentSectors?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -26692,6 +26978,9 @@ export type SectorAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -26701,6 +26990,7 @@ export type SectorAddInput = { roles?: InputMaybe>>; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -26811,7 +27101,7 @@ export type SecurityCoverage = BasicObject & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; periodicity?: Maybe; pirInformation?: Maybe; @@ -27004,6 +27294,10 @@ export type SecurityCoverageAddInput = { duration?: InputMaybe; externalReferences?: InputMaybe>>; external_uri?: InputMaybe; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; modified?: InputMaybe; name: Scalars['String']['input']; objectCovered: Scalars['String']['input']; @@ -27089,7 +27383,7 @@ export type SecurityPlatform = BasicObject & Identity & StixCoreObject & StixDom observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -27271,6 +27565,10 @@ export type SecurityPlatformAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; modified?: InputMaybe; name: Scalars['String']['input']; objectLabel?: InputMaybe>>; @@ -27279,6 +27577,7 @@ export type SecurityPlatformAddInput = { security_platform_type?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -27371,6 +27670,7 @@ export type Settings = BasicObject & InternalObject & IntlSettings & ThemeSettin platform_providers: Array; platform_reference_attachment?: Maybe; platform_session_idle_timeout?: Maybe; + platform_session_max_concurrent?: Maybe; platform_session_timeout?: Maybe; platform_theme?: Maybe; platform_title?: Maybe; @@ -27479,7 +27779,7 @@ export type Software = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -27656,9 +27956,13 @@ export type SoftwareToStixArgs = { export type SoftwareAddInput = { cpe?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; languages?: InputMaybe>>; name?: InputMaybe; swid?: InputMaybe; + upsertOperations?: InputMaybe>; vendor?: InputMaybe; version?: InputMaybe; x_opencti_product?: InputMaybe; @@ -27786,7 +28090,7 @@ export type StixCoreObject = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -28172,7 +28476,7 @@ export type StixCoreRelationship = BasicRelationship & StixRelationship & { objectMarking?: Maybe>; objectOrganization?: Maybe>; opinions?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; reports?: Maybe; @@ -28283,6 +28587,7 @@ export type StixCoreRelationshipAddInput = { stop_time?: InputMaybe; toId: Scalars['StixRef']['input']; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -28467,7 +28772,7 @@ export type StixCyberObservable = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -28771,7 +29076,7 @@ export type StixDomainObject = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -29171,7 +29476,7 @@ export type StixFile = BasicObject & HashedObservable & StixCoreObject & StixCyb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -29346,6 +29651,9 @@ export type StixFileAddInput = { atime?: InputMaybe; ctime?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; hashes?: InputMaybe>>; magic_number_hex?: InputMaybe; mime_type?: InputMaybe; @@ -29354,6 +29662,7 @@ export type StixFileAddInput = { name_enc?: InputMaybe; obsContent?: InputMaybe; size?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_additional_names?: InputMaybe>>; }; @@ -29378,7 +29687,7 @@ export type StixMetaObject = { is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; spec_version: Scalars['String']['output']; standard_id: Scalars['String']['output']; @@ -29416,7 +29725,7 @@ export type StixObject = { id: Scalars['ID']['output']; is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -29497,7 +29806,7 @@ export type StixRefRelationship = BasicRelationship & StixRelationship & { notes?: Maybe; objectMarking?: Maybe>; opinions?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; reports?: Maybe; @@ -29556,6 +29865,9 @@ export type StixRefRelationshipAddInput = { created?: InputMaybe; createdBy?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; fromId?: InputMaybe; modified?: InputMaybe; objectLabel?: InputMaybe>>; @@ -29638,7 +29950,7 @@ export type StixRelationship = { metrics?: Maybe>>; modified?: Maybe; objectMarking?: Maybe>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; representative: Representative; @@ -29766,7 +30078,7 @@ export type StixSightingRelationship = BasicRelationship & StixRelationship & { objectMarking?: Maybe>; objectOrganization?: Maybe>; opinions?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; reports?: Maybe; @@ -29846,6 +30158,7 @@ export type StixSightingRelationshipAddInput = { stix_id?: InputMaybe; toId: Scalars['StixRef']['input']; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_negative?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -30209,7 +30522,7 @@ export type SupportPackage = BasicObject & InternalObject & { package_status: PackageStatus; package_upload_dir?: Maybe; package_url?: Maybe; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -30251,12 +30564,20 @@ export type Synchronizer = { ssl_verify?: Maybe; stream_id: Scalars['String']['output']; synchronized?: Maybe; + toConfigurationExport: Scalars['String']['output']; token?: Maybe; uri: Scalars['String']['output']; user?: Maybe; }; +export type SynchronizerAddAutoUserInput = { + confidence_level: Scalars['Int']['input']; + user_name: Scalars['String']['input']; +}; + export type SynchronizerAddInput = { + automatic_user?: InputMaybe; + confidence_level?: InputMaybe; current_state_date?: InputMaybe; listen_deletion: Scalars['Boolean']['input']; name: Scalars['String']['input']; @@ -30267,7 +30588,19 @@ export type SynchronizerAddInput = { synchronized?: InputMaybe; token?: InputMaybe; uri: Scalars['String']['input']; - user_id?: InputMaybe; + user_id: Scalars['String']['input']; +}; + +export type SynchronizerAddInputFromImport = { + __typename?: 'SynchronizerAddInputFromImport'; + current_state_date?: Maybe; + listen_deletion: Scalars['Boolean']['output']; + name: Scalars['String']['output']; + no_dependencies: Scalars['Boolean']['output']; + ssl_verify: Scalars['Boolean']['output']; + stream_id: Scalars['String']['output']; + synchronized: Scalars['Boolean']['output']; + uri: Scalars['String']['output']; }; export type SynchronizerConnection = { @@ -30349,7 +30682,7 @@ export type System = BasicObject & Identity & StixCoreObject & StixDomainObject opinions?: Maybe; opinions_metrics?: Maybe; organizations?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -30535,6 +30868,9 @@ export type SystemAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -30545,6 +30881,7 @@ export type SystemAddInput = { roles?: InputMaybe>>; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_firstname?: InputMaybe; x_opencti_lastname?: InputMaybe; @@ -30655,7 +30992,7 @@ export type Task = BasicObject & Container & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -30868,6 +31205,10 @@ export type TaskAddInput = { createdBy?: InputMaybe; description?: InputMaybe; due_date?: InputMaybe; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; name: Scalars['String']['input']; objectAssignee?: InputMaybe>>; objectLabel?: InputMaybe>>; @@ -30876,6 +31217,7 @@ export type TaskAddInput = { objectParticipant?: InputMaybe>>; objects?: InputMaybe>>; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_workflow_id?: InputMaybe; }; @@ -30901,7 +31243,7 @@ export type TaskTemplate = BasicObject & InternalObject & { metrics?: Maybe>>; modified?: Maybe; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -31001,6 +31343,18 @@ export enum TaxiiCollectionOrdering { Name = 'name' } +export type TaxiiFeedAddInputFromImport = { + __typename?: 'TaxiiFeedAddInputFromImport'; + added_after_start?: Maybe; + authentication_type: Scalars['String']['output']; + authentication_value: Scalars['String']['output']; + collection: Scalars['String']['output']; + description: Scalars['String']['output']; + name: Scalars['String']['output']; + uri: Scalars['String']['output']; + version: Scalars['String']['output']; +}; + export enum TaxiiVersion { V1 = 'v1', V2 = 'v2', @@ -31037,7 +31391,7 @@ export type Text = BasicObject & StixCoreObject & StixCyberObservable & StixObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -31209,6 +31563,10 @@ export type TextToStixArgs = { export type TextAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -31315,7 +31673,7 @@ export type ThreatActor = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; personal_motivations?: Maybe>>; pirInformation?: Maybe; @@ -31549,7 +31907,7 @@ export type ThreatActorGroup = BasicObject & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; personal_motivations?: Maybe>>; pirInformation?: Maybe; @@ -31737,6 +32095,9 @@ export type ThreatActorGroupAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; goals?: InputMaybe>>; lang?: InputMaybe; @@ -31757,6 +32118,7 @@ export type ThreatActorGroupAddInput = { stix_id?: InputMaybe; threat_actor_types?: InputMaybe>>; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -31859,7 +32221,7 @@ export type ThreatActorIndividual = BasicObject & StixCoreObject & StixDomainObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; personal_motivations?: Maybe>>; pirInformation?: Maybe; @@ -32053,6 +32415,9 @@ export type ThreatActorIndividualAddInput = { externalReferences?: InputMaybe>>; eye_color?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; first_seen?: InputMaybe; gender?: InputMaybe; goals?: InputMaybe>>; @@ -32078,6 +32443,7 @@ export type ThreatActorIndividualAddInput = { stix_id?: InputMaybe; threat_actor_types?: InputMaybe>>; update?: InputMaybe; + upsertOperations?: InputMaybe>; weight?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; @@ -32177,7 +32543,7 @@ export type Tool = BasicObject & StixCoreObject & StixDomainObject & StixObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -32360,6 +32726,9 @@ export type ToolAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; killChainPhases?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; @@ -32372,6 +32741,7 @@ export type ToolAddInput = { tool_types?: InputMaybe>>; tool_version?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_modified_at?: InputMaybe; x_opencti_stix_ids?: InputMaybe>>; x_opencti_workflow_id?: InputMaybe; @@ -32463,7 +32833,7 @@ export type TrackingNumber = BasicObject & StixCoreObject & StixCyberObservable observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -32639,6 +33009,10 @@ export type TrackingNumberToStixArgs = { export type TrackingNumberAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -32658,7 +33032,7 @@ export type Trigger = BasicObject & InternalObject & { modified?: Maybe; name: Scalars['String']['output']; notifiers?: Maybe>; - parent_types: Array>; + parent_types: Array; period?: Maybe; recipients?: Maybe>; refreshed_at?: Maybe; @@ -32807,7 +33181,7 @@ export type Url = BasicObject & StixCoreObject & StixCyberObservable & StixObjec observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -32979,6 +33353,10 @@ export type UrlToStixArgs = { export type UrlAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -33014,7 +33392,7 @@ export type User = BasicObject & InternalObject & { otp_activated?: Maybe; otp_mandatory?: Maybe; otp_qr?: Maybe; - parent_types: Array>; + parent_types: Array; personal_notifiers?: Maybe>; refreshed_at?: Maybe; restrict_delete?: Maybe; @@ -33101,7 +33479,7 @@ export type UserAccount = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -33283,9 +33661,13 @@ export type UserAccountAddInput = { credential_last_changed?: InputMaybe; display_name?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; is_disabled?: InputMaybe; is_privileged?: InputMaybe; is_service_account?: InputMaybe; + upsertOperations?: InputMaybe>; user_id?: InputMaybe; }; @@ -33342,7 +33724,7 @@ export type UserAgent = BasicObject & StixCoreObject & StixCyberObservable & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -33514,6 +33896,10 @@ export type UserAgentToStixArgs = { export type UserAgentAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; + upsertOperations?: InputMaybe>; value?: InputMaybe; }; @@ -33654,7 +34040,7 @@ export type Vocabulary = BasicObject & StixMetaObject & StixObject & { modified?: Maybe; name: Scalars['String']['output']; order?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -33817,7 +34203,7 @@ export type Vulnerability = BasicObject & StixCoreObject & StixDomainObject & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -34056,6 +34442,9 @@ export type VulnerabilityAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -34065,6 +34454,7 @@ export type VulnerabilityAddInput = { revoked?: InputMaybe; stix_id?: InputMaybe; update?: InputMaybe; + upsertOperations?: InputMaybe>; x_opencti_aliases?: InputMaybe>>; x_opencti_cisa_kev?: InputMaybe; x_opencti_cvss_attack_complexity?: InputMaybe; @@ -34262,7 +34652,7 @@ export type WindowsRegistryKey = BasicObject & StixCoreObject & StixCyberObserva observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -34434,8 +34824,12 @@ export type WindowsRegistryKeyToStixArgs = { export type WindowsRegistryKeyAddInput = { attribute_key?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; modified_time?: InputMaybe; number_of_subkeys?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type WindowsRegistryValueType = BasicObject & StixCoreObject & StixCyberObservable & StixObject & { @@ -34471,7 +34865,7 @@ export type WindowsRegistryValueType = BasicObject & StixCoreObject & StixCyberO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -34644,7 +35038,11 @@ export type WindowsRegistryValueTypeAddInput = { data?: InputMaybe; data_type?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; name?: InputMaybe; + upsertOperations?: InputMaybe>; }; export type Work = { @@ -34866,7 +35264,7 @@ export type X509Certificate = BasicObject & HashedObservable & StixCoreObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; policy_constraints?: Maybe; policy_mappings?: Maybe; @@ -35058,6 +35456,9 @@ export type X509CertificateAddInput = { crl_distribution_points?: InputMaybe; extended_key_usage?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; + files?: InputMaybe>>; + filesMarkings?: InputMaybe>>>>; hashes?: InputMaybe>>; inhibit_any_policy?: InputMaybe; is_self_signed?: InputMaybe; @@ -35078,6 +35479,7 @@ export type X509CertificateAddInput = { subject_public_key_algorithm?: InputMaybe; subject_public_key_exponent?: InputMaybe; subject_public_key_modulus?: InputMaybe; + upsertOperations?: InputMaybe>; validity_not_after?: InputMaybe; validity_not_before?: InputMaybe; version?: InputMaybe; @@ -36351,6 +36753,7 @@ export type ResolversTypes = ResolversObject<{ IngestionRssEdge: ResolverTypeWrapper & { node: ResolversTypes['IngestionRss'] }>; IngestionRssOrdering: IngestionRssOrdering; IngestionTaxii: ResolverTypeWrapper; + IngestionTaxiiAddAutoUserInput: IngestionTaxiiAddAutoUserInput; IngestionTaxiiAddInput: IngestionTaxiiAddInput; IngestionTaxiiCollection: ResolverTypeWrapper; IngestionTaxiiCollectionAddInput: IngestionTaxiiCollectionAddInput; @@ -36657,6 +37060,7 @@ export type ResolversTypes = ResolversObject<{ RuleExecutionError: ResolverTypeWrapper; RuleManager: ResolverTypeWrapper; RuleTask: ResolverTypeWrapper & { work?: Maybe }>; + S3Connection: ResolverTypeWrapper; SSHKey: ResolverTypeWrapper & { cases?: Maybe, connectors?: Maybe>>, containers?: Maybe, createdBy?: Maybe, editContext?: Maybe>, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, jobs?: Maybe>>, notes?: Maybe, objectLabel?: Maybe>, objectMarking?: Maybe>, objectOrganization?: Maybe>, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreObjectsDistribution?: Maybe>>, stixCoreRelationships?: Maybe, stixCoreRelationshipsDistribution?: Maybe>>, x_opencti_inferences?: Maybe>> }>; SSHKeyAddInput: SshKeyAddInput; SavedFilter: ResolverTypeWrapper; @@ -36801,7 +37205,9 @@ export type ResolversTypes = ResolversObject<{ SupportPackageForceZipInput: SupportPackageForceZipInput; SupportPackageOrdering: SupportPackageOrdering; Synchronizer: ResolverTypeWrapper; + SynchronizerAddAutoUserInput: SynchronizerAddAutoUserInput; SynchronizerAddInput: SynchronizerAddInput; + SynchronizerAddInputFromImport: ResolverTypeWrapper; SynchronizerConnection: ResolverTypeWrapper; SynchronizerEdge: ResolverTypeWrapper; SynchronizerEditMutations: ResolverTypeWrapper; @@ -36829,6 +37235,7 @@ export type ResolversTypes = ResolversObject<{ TaxiiCollectionEdge: ResolverTypeWrapper; TaxiiCollectionEditMutations: ResolverTypeWrapper; TaxiiCollectionOrdering: TaxiiCollectionOrdering; + TaxiiFeedAddInputFromImport: ResolverTypeWrapper; TaxiiVersion: TaxiiVersion; Text: ResolverTypeWrapper & { cases?: Maybe, connectors?: Maybe>>, containers?: Maybe, createdBy?: Maybe, editContext?: Maybe>, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, jobs?: Maybe>>, notes?: Maybe, objectLabel?: Maybe>, objectMarking?: Maybe>, objectOrganization?: Maybe>, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreObjectsDistribution?: Maybe>>, stixCoreRelationships?: Maybe, stixCoreRelationshipsDistribution?: Maybe>>, x_opencti_inferences?: Maybe>> }>; TextAddInput: TextAddInput; @@ -37316,6 +37723,7 @@ export type ResolversParentTypes = ResolversObject<{ IngestionRssConnection: Omit & { edges: Array }; IngestionRssEdge: Omit & { node: ResolversParentTypes['IngestionRss'] }; IngestionTaxii: BasicStoreEntityIngestionTaxii; + IngestionTaxiiAddAutoUserInput: IngestionTaxiiAddAutoUserInput; IngestionTaxiiAddInput: IngestionTaxiiAddInput; IngestionTaxiiCollection: BasicStoreEntityIngestionTaxiiCollection; IngestionTaxiiCollectionAddInput: IngestionTaxiiCollectionAddInput; @@ -37584,6 +37992,7 @@ export type ResolversParentTypes = ResolversObject<{ RuleExecutionError: RuleExecutionError; RuleManager: RuleManager; RuleTask: Omit & { work?: Maybe }; + S3Connection: S3Connection; SSHKey: Omit & { cases?: Maybe, connectors?: Maybe>>, containers?: Maybe, createdBy?: Maybe, editContext?: Maybe>, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, jobs?: Maybe>>, notes?: Maybe, objectLabel?: Maybe>, objectMarking?: Maybe>, objectOrganization?: Maybe>, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreObjectsDistribution?: Maybe>>, stixCoreRelationships?: Maybe, stixCoreRelationshipsDistribution?: Maybe>>, x_opencti_inferences?: Maybe>> }; SSHKeyAddInput: SshKeyAddInput; SavedFilter: BasicStoreEntitySavedFilter; @@ -37707,7 +38116,9 @@ export type ResolversParentTypes = ResolversObject<{ SupportPackageEdge: Omit & { node: ResolversParentTypes['SupportPackage'] }; SupportPackageForceZipInput: SupportPackageForceZipInput; Synchronizer: Synchronizer; + SynchronizerAddAutoUserInput: SynchronizerAddAutoUserInput; SynchronizerAddInput: SynchronizerAddInput; + SynchronizerAddInputFromImport: SynchronizerAddInputFromImport; SynchronizerConnection: SynchronizerConnection; SynchronizerEdge: SynchronizerEdge; SynchronizerEditMutations: SynchronizerEditMutations; @@ -37730,6 +38141,7 @@ export type ResolversParentTypes = ResolversObject<{ TaxiiCollectionConnection: TaxiiCollectionConnection; TaxiiCollectionEdge: TaxiiCollectionEdge; TaxiiCollectionEditMutations: TaxiiCollectionEditMutations; + TaxiiFeedAddInputFromImport: TaxiiFeedAddInputFromImport; Text: Omit & { cases?: Maybe, connectors?: Maybe>>, containers?: Maybe, createdBy?: Maybe, editContext?: Maybe>, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, jobs?: Maybe>>, notes?: Maybe, objectLabel?: Maybe>, objectMarking?: Maybe>, objectOrganization?: Maybe>, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreObjectsDistribution?: Maybe>>, stixCoreRelationships?: Maybe, stixCoreRelationshipsDistribution?: Maybe>>, x_opencti_inferences?: Maybe>> }; TextAddInput: TextAddInput; Theme: BasicStoreEntityTheme; @@ -37906,7 +38318,7 @@ export type AdministrativeAreaResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -38043,7 +38455,7 @@ export type ArtifactResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; payload_bin?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38124,7 +38536,7 @@ export type AttackPatternResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentAttackPatterns?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38278,7 +38690,7 @@ export type AutonomousSystemResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -38374,7 +38786,7 @@ export type BankAccountResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -38399,7 +38811,7 @@ export type BasicObjectResolvers; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; }>; @@ -38411,7 +38823,7 @@ export type BasicRelationshipResolvers, ParentType, ContextType>; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; toRole?: Resolver, ParentType, ContextType>; @@ -38470,7 +38882,7 @@ export type CampaignResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38522,7 +38934,7 @@ export type CapabilityResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -38584,7 +38996,7 @@ export type CaseResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38662,7 +39074,7 @@ export type CaseIncidentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; priority?: Resolver, ParentType, ContextType>; @@ -38747,7 +39159,7 @@ export type CaseRfiResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; priority?: Resolver, ParentType, ContextType>; @@ -38831,7 +39243,7 @@ export type CaseRftResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; priority?: Resolver, ParentType, ContextType>; @@ -38877,7 +39289,7 @@ export type CaseTemplateResolvers>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; tasks?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -38961,7 +39373,7 @@ export type ChannelResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39038,7 +39450,7 @@ export type CityResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -39146,7 +39558,7 @@ export type ConnectorResolvers>>, ParentType, ContextType>; name?: Resolver; only_contextual?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; playbook_compatible?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; @@ -39166,6 +39578,7 @@ export type ConnectorConfigResolvers; push_exchange?: Resolver; push_routing?: Resolver; + s3?: Resolver; }>; export type ConnectorConfigurationResolvers = ResolversObject<{ @@ -39204,7 +39617,7 @@ export type ConnectorManagerResolvers, ParentType, ContextType>; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; public_key?: Resolver; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -39262,7 +39675,7 @@ export type ContainerResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relatedContainers?: Resolver, ParentType, ContextType, Partial>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39357,7 +39770,7 @@ export type CountryResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -39439,7 +39852,7 @@ export type CourseOfActionResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39537,7 +39950,7 @@ export type CredentialResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39587,7 +40000,7 @@ export type CryptocurrencyWalletResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39637,7 +40050,7 @@ export type CryptographicKeyResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39782,7 +40195,7 @@ export type DataComponentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39854,7 +40267,7 @@ export type DataSourceResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39909,7 +40322,7 @@ export type DecayExclusionRuleResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -39952,7 +40365,7 @@ export type DecayRuleResolvers>>, ParentType, ContextType>; name?: Resolver; order?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -40065,7 +40478,7 @@ export type DirectoryResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; path?: Resolver; path_enc?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; @@ -40111,7 +40524,7 @@ export type DisseminationListResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -40167,7 +40580,7 @@ export type DomainNameResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -40295,7 +40708,7 @@ export type EmailAddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -40350,7 +40763,7 @@ export type EmailMessageResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; received_lines?: Resolver>>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -40404,7 +40817,7 @@ export type EmailMimePartTypeResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -40431,7 +40844,7 @@ export type EmailTemplateResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; sender_email?: Resolver; standard_id?: Resolver; template_body?: Resolver; @@ -40521,7 +40934,7 @@ export type EventResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -40568,7 +40981,7 @@ export type ExclusionListResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -40613,7 +41026,7 @@ export type ExternalReferenceResolvers>>, ParentType, ContextType, Partial>; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver>; references?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -40731,7 +41144,7 @@ export type FeedbackResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; rating?: Resolver, ParentType, ContextType>; @@ -40849,7 +41262,7 @@ export type FintelDesignResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; textColor?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -40873,7 +41286,7 @@ export type FintelTemplateResolvers, ParentType, ContextType>; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; settings_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; start_date?: Resolver, ParentType, ContextType>; @@ -40955,7 +41368,7 @@ export type GroupResolvers; no_creators?: Resolver, ParentType, ContextType>; not_shareable_marking_types?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; restrict_delete?: Resolver, ParentType, ContextType>; roles?: Resolver, ParentType, ContextType, Partial>; @@ -41029,7 +41442,7 @@ export type GroupingResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41101,7 +41514,7 @@ export type HashedObservableResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41148,7 +41561,7 @@ export type HostnameResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41199,7 +41612,7 @@ export type IPv4AddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41250,7 +41663,7 @@ export type IPv6AddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41309,7 +41722,7 @@ export type IdentityResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41392,7 +41805,7 @@ export type IncidentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41507,7 +41920,7 @@ export type IndicatorResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pattern?: Resolver, ParentType, ContextType>; pattern_type?: Resolver, ParentType, ContextType>; pattern_version?: Resolver, ParentType, ContextType>; @@ -41605,7 +42018,7 @@ export type IndividualResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; organizations?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41704,7 +42117,7 @@ export type InfrastructureResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41763,7 +42176,7 @@ export type IngestionCsvResolvers>, ParentType, ContextType>; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; scheduling_period?: Resolver, ParentType, ContextType>; standard_id?: Resolver; @@ -41809,7 +42222,7 @@ export type IngestionJsonResolvers, ParentType, ContextType>; pagination_with_sub_page_attribute_path?: Resolver, ParentType, ContextType>; pagination_with_sub_page_query_verb?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; query_attributes?: Resolver>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; scheduling_period?: Resolver, ParentType, ContextType>; @@ -41893,6 +42306,7 @@ export type IngestionTaxiiResolvers, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; + toConfigurationExport?: Resolver; updated_at?: Resolver, ParentType, ContextType>; uri?: Resolver; user?: Resolver, ParentType, ContextType>; @@ -41954,7 +42368,7 @@ export type InternalRelationshipResolvers, ParentType, ContextType>; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; to?: Resolver, ParentType, ContextType>; @@ -42010,7 +42424,7 @@ export type IntrusionSetResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; primary_motivation?: Resolver, ParentType, ContextType>; @@ -42160,7 +42574,7 @@ export type KillChainPhaseResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; phase_name?: Resolver; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; @@ -42205,7 +42619,7 @@ export type LabelResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -42272,7 +42686,7 @@ export type LanguageResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -42363,7 +42777,7 @@ export type LocationResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; precision?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -42469,7 +42883,7 @@ export type MacAddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -42535,7 +42949,7 @@ export type MalwareResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -42603,7 +43017,7 @@ export type MalwareAnalysisResolvers, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; product?: Resolver; @@ -42678,7 +43092,7 @@ export type ManagedConnectorResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -42734,7 +43148,7 @@ export type MarkingDefinitionResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -42877,7 +43291,7 @@ export type MediaContentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; publication_date?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43155,6 +43569,7 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; ingestionRssFieldPatch?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiAdd?: Resolver, ParentType, ContextType, RequireFields>; + ingestionTaxiiAddAutoUser?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiCollectionAdd?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiCollectionDelete?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiCollectionFieldPatch?: Resolver, ParentType, ContextType, RequireFields>; @@ -43279,6 +43694,7 @@ export type MutationResolvers>; ruleSetActivation?: Resolver>; rulesRescan?: Resolver, ParentType, ContextType, RequireFields>; + runMigration?: Resolver>; savedFilterAdd?: Resolver, ParentType, ContextType, RequireFields>; savedFilterDelete?: Resolver, ParentType, ContextType, RequireFields>; savedFilterFieldPatch?: Resolver, ParentType, ContextType, RequireFields>; @@ -43338,6 +43754,7 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; supportPackageForceZip?: Resolver, ParentType, ContextType, RequireFields>; synchronizerAdd?: Resolver, ParentType, ContextType, RequireFields>; + synchronizerAddAutoUser?: Resolver, ParentType, ContextType, RequireFields>; synchronizerEdit?: Resolver, ParentType, ContextType, RequireFields>; synchronizerStart?: Resolver, ParentType, ContextType, RequireFields>; synchronizerStop?: Resolver, ParentType, ContextType, RequireFields>; @@ -43443,7 +43860,7 @@ export type MutexResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -43508,7 +43925,7 @@ export type NarrativeResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentNarratives?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43578,7 +43995,7 @@ export type NetworkTrafficResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; protocols?: Resolver>>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43649,7 +44066,7 @@ export type NoteResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43702,7 +44119,7 @@ export type NotificationResolvers; notification_content?: Resolver, ParentType, ContextType>; notification_type?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver, ParentType, ContextType>; @@ -43748,7 +44165,7 @@ export type NotifierResolvers; notifier_connector?: Resolver; notifier_connector_id?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -43838,7 +44255,7 @@ export type ObservedDataResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43933,7 +44350,7 @@ export type OpinionResolvers; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -44030,7 +44447,7 @@ export type OrganizationResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentOrganizations?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -44149,7 +44566,7 @@ export type PaymentCardResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -44198,7 +44615,7 @@ export type PersonaResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; persona_name?: Resolver; persona_type?: Resolver; @@ -44249,7 +44666,7 @@ export type PhoneNumberResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -44335,7 +44752,7 @@ export type PirRelationshipResolvers; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pir_explanation?: Resolver>, ParentType, ContextType>; pir_score?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -44513,7 +44930,7 @@ export type PositionResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; postal_code?: Resolver, ParentType, ContextType>; @@ -44601,7 +45018,7 @@ export type ProcessResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; owner_sid?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pid?: Resolver, ParentType, ContextType>; priority?: Resolver, ParentType, ContextType>; @@ -45047,6 +45464,7 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; supportPackages?: Resolver, ParentType, ContextType, Partial>; synchronizer?: Resolver, ParentType, ContextType, RequireFields>; + synchronizerAddInputFromImport?: Resolver>; synchronizerFetch?: Resolver>>, ParentType, ContextType, Partial>; synchronizers?: Resolver, ParentType, ContextType, Partial>; system?: Resolver, ParentType, ContextType, Partial>; @@ -45059,6 +45477,7 @@ export type QueryResolvers, ParentType, ContextType, Partial>; taxiiCollection?: Resolver, ParentType, ContextType, RequireFields>; taxiiCollections?: Resolver, ParentType, ContextType, Partial>; + taxiiFeedAddInputFromImport?: Resolver>; theme?: Resolver, ParentType, ContextType, RequireFields>; themes?: Resolver, ParentType, ContextType, Partial>; threatActor?: Resolver, ParentType, ContextType, Partial>; @@ -45195,7 +45614,7 @@ export type RegionResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentRegions?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -45292,7 +45711,7 @@ export type ReportResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; published?: Resolver, ParentType, ContextType>; @@ -45425,7 +45844,7 @@ export type RoleResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -45491,6 +45910,16 @@ export type RuleTaskResolvers; }>; +export type S3ConnectionResolvers = ResolversObject<{ + access_key?: Resolver; + bucket_name?: Resolver; + bucket_region?: Resolver; + endpoint?: Resolver; + port?: Resolver; + secret_key?: Resolver; + use_ssl?: Resolver; +}>; + export type SshKeyResolvers = ResolversObject<{ cases?: Resolver, ParentType, ContextType, Partial>; comment?: Resolver, ParentType, ContextType>; @@ -45527,7 +45956,7 @@ export type SshKeyResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; public_key?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -45554,7 +45983,7 @@ export type SavedFilterResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; scope?: Resolver; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -45620,7 +46049,7 @@ export type SectorResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentSectors?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -45714,7 +46143,7 @@ export type SecurityCoverageResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; periodicity?: Resolver, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -45793,7 +46222,7 @@ export type SecurityPlatformResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -45887,6 +46316,7 @@ export type SettingsResolvers, ParentType, ContextType>; platform_reference_attachment?: Resolver, ParentType, ContextType>; platform_session_idle_timeout?: Resolver, ParentType, ContextType>; + platform_session_max_concurrent?: Resolver, ParentType, ContextType>; platform_session_timeout?: Resolver, ParentType, ContextType>; platform_theme?: Resolver, ParentType, ContextType>; platform_title?: Resolver, ParentType, ContextType>; @@ -45964,7 +46394,7 @@ export type SoftwareResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46066,7 +46496,7 @@ export type StixCoreObjectResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46147,7 +46577,7 @@ export type StixCoreRelationshipResolvers>, ParentType, ContextType>; objectOrganization?: Resolver>, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46225,7 +46655,7 @@ export type StixCyberObservableResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46303,7 +46733,7 @@ export type StixDomainObjectResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -46394,7 +46824,7 @@ export type StixFileResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46440,7 +46870,7 @@ export type StixMetaObjectResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; spec_version?: Resolver; standard_id?: Resolver; @@ -46467,7 +46897,7 @@ export type StixObjectResolvers; is_inferred?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -46534,7 +46964,7 @@ export type StixRefRelationshipResolvers, ParentType, ContextType, Partial>; objectMarking?: Resolver>, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46584,7 +47014,7 @@ export type StixRelationshipResolvers>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; objectMarking?: Resolver>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; representative?: Resolver; @@ -46658,7 +47088,7 @@ export type StixSightingRelationshipResolvers>, ParentType, ContextType>; objectOrganization?: Resolver>, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46803,7 +47233,7 @@ export type SupportPackageResolvers; package_upload_dir?: Resolver, ParentType, ContextType>; package_url?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -46829,11 +47259,23 @@ export type SynchronizerResolvers, ParentType, ContextType>; stream_id?: Resolver; synchronized?: Resolver, ParentType, ContextType>; + toConfigurationExport?: Resolver; token?: Resolver, ParentType, ContextType>; uri?: Resolver; user?: Resolver, ParentType, ContextType>; }>; +export type SynchronizerAddInputFromImportResolvers = ResolversObject<{ + current_state_date?: Resolver, ParentType, ContextType>; + listen_deletion?: Resolver; + name?: Resolver; + no_dependencies?: Resolver; + ssl_verify?: Resolver; + stream_id?: Resolver; + synchronized?: Resolver; + uri?: Resolver; +}>; + export type SynchronizerConnectionResolvers = ResolversObject<{ edges?: Resolver>>, ParentType, ContextType>; pageInfo?: Resolver; @@ -46888,7 +47330,7 @@ export type SystemResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; organizations?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -46979,7 +47421,7 @@ export type TaskResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47021,7 +47463,7 @@ export type TaskTemplateResolvers>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -47062,6 +47504,17 @@ export type TaxiiCollectionEditMutationsResolvers, ParentType, ContextType, RequireFields>; }>; +export type TaxiiFeedAddInputFromImportResolvers = ResolversObject<{ + added_after_start?: Resolver, ParentType, ContextType>; + authentication_type?: Resolver; + authentication_value?: Resolver; + collection?: Resolver; + description?: Resolver; + name?: Resolver; + uri?: Resolver; + version?: Resolver; +}>; + export type TextResolvers = ResolversObject<{ cases?: Resolver, ParentType, ContextType, Partial>; connectors?: Resolver>>, ParentType, ContextType, Partial>; @@ -47091,7 +47544,7 @@ export type TextResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47194,7 +47647,7 @@ export type ThreatActorResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; personal_motivations?: Resolver>>, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -47275,7 +47728,7 @@ export type ThreatActorGroupResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; personal_motivations?: Resolver>>, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -47375,7 +47828,7 @@ export type ThreatActorIndividualResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; personal_motivations?: Resolver>>, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -47460,7 +47913,7 @@ export type ToolResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47533,7 +47986,7 @@ export type TrackingNumberResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47569,7 +48022,7 @@ export type TriggerResolvers, ParentType, ContextType>; name?: Resolver; notifiers?: Resolver>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; period?: Resolver, ParentType, ContextType>; recipients?: Resolver>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47638,7 +48091,7 @@ export type UrlResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47690,7 +48143,7 @@ export type UserResolvers, ParentType, ContextType>; otp_mandatory?: Resolver, ParentType, ContextType>; otp_qr?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; personal_notifiers?: Resolver>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; restrict_delete?: Resolver, ParentType, ContextType>; @@ -47751,7 +48204,7 @@ export type UserAccountResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47801,7 +48254,7 @@ export type UserAgentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47875,7 +48328,7 @@ export type VocabularyResolvers, ParentType, ContextType>; name?: Resolver; order?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -47947,7 +48400,7 @@ export type VulnerabilityResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -48125,7 +48578,7 @@ export type WindowsRegistryKeyResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -48177,7 +48630,7 @@ export type WindowsRegistryValueTypeResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -48327,7 +48780,7 @@ export type X509CertificateResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; policy_constraints?: Resolver, ParentType, ContextType>; policy_mappings?: Resolver, ParentType, ContextType>; @@ -48845,6 +49298,7 @@ export type Resolvers = ResolversObject<{ RuleExecutionError?: RuleExecutionErrorResolvers; RuleManager?: RuleManagerResolvers; RuleTask?: RuleTaskResolvers; + S3Connection?: S3ConnectionResolvers; SSHKey?: SshKeyResolvers; SavedFilter?: SavedFilterResolvers; SavedFilterConnection?: SavedFilterConnectionResolvers; @@ -48937,6 +49391,7 @@ export type Resolvers = ResolversObject<{ SupportPackageConnection?: SupportPackageConnectionResolvers; SupportPackageEdge?: SupportPackageEdgeResolvers; Synchronizer?: SynchronizerResolvers; + SynchronizerAddInputFromImport?: SynchronizerAddInputFromImportResolvers; SynchronizerConnection?: SynchronizerConnectionResolvers; SynchronizerEdge?: SynchronizerEdgeResolvers; SynchronizerEditMutations?: SynchronizerEditMutationsResolvers; @@ -48954,6 +49409,7 @@ export type Resolvers = ResolversObject<{ TaxiiCollectionConnection?: TaxiiCollectionConnectionResolvers; TaxiiCollectionEdge?: TaxiiCollectionEdgeResolvers; TaxiiCollectionEditMutations?: TaxiiCollectionEditMutationsResolvers; + TaxiiFeedAddInputFromImport?: TaxiiFeedAddInputFromImportResolvers; Text?: TextResolvers; Theme?: ThemeResolvers; ThemeConnection?: ThemeConnectionResolvers; diff --git a/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js b/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js index e17f0080e84a..456e41964f90 100644 --- a/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js +++ b/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js @@ -1,5 +1,5 @@ +import { once } from 'events'; import * as jsonpatch from 'fast-json-patch'; -import { Promise } from 'bluebird'; import { LRUCache } from 'lru-cache'; import { now } from 'moment'; import conf, { basePath, logApp } from '../config/conf'; @@ -74,12 +74,8 @@ const createBroadcastClient = (channel) => { setChannelDelay: (d) => channel.setDelay(d), setLastEventId: (id) => channel.setLastEventId(id), close: () => channel.close(), - sendEvent: (eventId, topic, event) => { - channel.sendEvent(eventId, topic, event); - }, - sendConnected: (streamInfo) => { - channel.sendEvent(undefined, 'connected', streamInfo); - }, + sendEvent: async (eventId, topic, event) => channel.sendEvent(eventId, topic, event), + sendConnected: async (streamInfo) => channel.sendEvent(undefined, 'connected', streamInfo), }; }; @@ -235,23 +231,56 @@ const createSseMiddleware = () => { const initBroadcasting = async (req, res, client, processor) => { const broadcasterInfo = processor ? await processor.info() : {}; - req.on('close', () => { + let closed = false; + const close = () => { + if (closed) { + return; + } client.close(); delete broadcastClients[client.id]; logApp.info(`[STREAM] Closing stream processor for ${client.id}`); processor.shutdown(); - }); + closed = true; + }; + req.on('close', close); // On closing the request + res.on('close', close); // On closing the response res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache, no-transform', // no-transform is required for dev proxy }); - client.sendConnected({ ...broadcasterInfo, connectionId: client.id }); broadcastClients[client.id] = client; + await client.sendConnected({ ...broadcasterInfo, connectionId: client.id }); }; + const createSseChannel = (req, res, startId) => { let lastEventId = startId; + + const buildMessage = (eventId, topic, event) => { + let message = ''; + if (eventId) { + message += `id: ${eventId}\n`; + } + if (topic) { + message += `event: ${topic}\n`; + } + if (event) { + message += 'data: '; + const isDataTopic = eventId && topic !== 'heartbeat'; + if (isDataTopic && req.user && !isUserHasCapability(req.user, KNOWLEDGE_ORGANIZATION_RESTRICT)) { + const filtered = { ...event }; + delete filtered.data.extensions[STIX_EXT_OCTI].granted_refs; + message += JSON.stringify(filtered); + } else { + message += JSON.stringify(event); + } + message += '\n'; + } + message += '\n'; + return message; + }; + const channel = { id: generateInternalId(), delay: parseInt(extractQueryParameter(req, 'delay') || req.headers['event-delay'] || 0, 10), @@ -266,34 +295,18 @@ const createSseMiddleware = () => { setLastEventId: (id) => { lastEventId = id; }, - connected: () => !res.finished, - sendEvent: (eventId, topic, event) => { + connected: () => !res.finished && res.writable, + sendEvent: async (eventId, topic, event) => { // Write on an already terminated response if (res.finished || !res.writable) { return; } - let message = ''; - if (eventId) { - lastEventId = eventId; - message += `id: ${eventId}\n`; + lastEventId = eventId || lastEventId; + const message = buildMessage(eventId, topic, event); + if (!res.write(message)) { + logApp.debug('[STREAM] Buffer draining', { buffer: res.writableLength, limit: res.writableHighWaterMark }); + await once(res, 'drain'); } - if (topic) { - message += `event: ${topic}\n`; - } - if (event) { - message += 'data: '; - const isDataTopic = eventId && topic !== 'heartbeat'; - if (isDataTopic && req.user && !isUserHasCapability(req.user, KNOWLEDGE_ORGANIZATION_RESTRICT)) { - const filtered = { ...event }; - delete filtered.data.extensions[STIX_EXT_OCTI].granted_refs; - message += JSON.stringify(filtered); - } else { - message += JSON.stringify(event); - } - message += '\n'; - } - message += '\n'; - res.write(message); res.flush(); }, close: () => { @@ -309,11 +322,17 @@ const createSseMiddleware = () => { } }, }; - const heartTimer = () => { - if (lastEventId) { - const [idTime] = lastEventId.split('-'); - const idDate = utcDate(parseInt(idTime, 10)).toISOString(); - channel.sendEvent(lastEventId, 'heartbeat', idDate); + const heartTimer = async () => { + try { + // heartbeat must be sent to maintain the connection + // Only when the last event is accessible and nothing is currently in the socket. + if (lastEventId && res.writableLength === 0) { + const [idTime] = lastEventId.split('-'); + const idDate = utcDate(parseInt(idTime, 10)).toISOString(); + await channel.sendEvent(lastEventId, 'heartbeat', idDate); + } + } catch { + // ignore } }; const heartbeatInterval = setInterval(heartTimer, HEARTBEAT_PERIOD); @@ -338,7 +357,7 @@ const createSseMiddleware = () => { const { id: eventId, event, data } = elements[index]; const instanceAccessible = await isUserCanAccessStixElement(context, user, data.data); if (instanceAccessible) { - client.sendEvent(eventId, event, data); + await client.sendEvent(eventId, event, data); } } client.setLastEventId(lastEventId); @@ -395,7 +414,7 @@ const createSseMiddleware = () => { const message = generateCreateMessage(missingInstance); const origin = { referer: EVENT_TYPE_DEPENDENCIES }; const content = { data: missingData, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); + await channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); cache.set(missingData.id, 'hit'); await wait(channel.delay); } @@ -421,7 +440,7 @@ const createSseMiddleware = () => { const message = generateCreateMessage(missingRelation); const origin = { referer: EVENT_TYPE_DEPENDENCIES }; const content = { data: stixRelation, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); + await channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); cache.set(stixRelation.id, 'hit'); } } @@ -447,7 +466,6 @@ const createSseMiddleware = () => { const entityTypeFilters = findFiltersFromKey(filters.filters, 'entity_type', 'eq'); const entityTypeFilter = entityTypeFilters.length > 0 ? entityTypeFilters[0] : undefined; const entityTypeFilterValues = entityTypeFilter?.values ?? []; - // eslint-disable-next-line no-restricted-syntax for (const id of entityTypeFilterValues) { // consider the operator if (entityTypeFilter.operator === 'not_eq') { @@ -501,7 +519,7 @@ const createSseMiddleware = () => { // From or to are visible, consider it as a dependency const origin = { referer: EVENT_TYPE_DEPENDENCIES }; const content = { data: stix, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, type, content); + await channel.sendEvent(eventId, type, content); } } }; @@ -600,18 +618,18 @@ const createSseMiddleware = () => { const { newDocument: previous } = jsonpatch.applyPatch(structuredClone(stix), evenContext.reverse_patch); const isPreviouslyVisible = await isStixMatchFilterGroup(context, user, previous, streamFilters); if (isPreviouslyVisible && !isCurrentlyVisible && publishDeletion) { // No longer visible - client.sendEvent(eventId, EVENT_TYPE_DELETE, eventData); + await client.sendEvent(eventId, EVENT_TYPE_DELETE, eventData); cache.set(stix.id, 'hit'); } else if (!isPreviouslyVisible && isCurrentlyVisible) { // Newly visible const isValidResolution = await resolveAndPublishDependencies(context, noDependencies, cache, channel, req, eventId, stix); if (isValidResolution) { - client.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); + await client.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); cache.set(stix.id, 'hit'); } } else if (isCurrentlyVisible) { // Just an update const isValidResolution = await resolveAndPublishDependencies(context, noDependencies, cache, channel, req, eventId, stix); if (isValidResolution) { - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } else if (isRelation && publishDependencies) { // Update but not visible - relation type @@ -630,20 +648,20 @@ const createSseMiddleware = () => { // At least one container is matching the filter, so publishing the event if (countRelatedContainers > 0) { await resolveAndPublishMissingRefs(context, cache, channel, req, eventId, stix); - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } } else if (isCurrentlyVisible) { if (type === EVENT_TYPE_DELETE) { if (publishDeletion) { - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } else { // Create and merge const isValidResolution = await resolveAndPublishDependencies(context, noDependencies, cache, channel, req, eventId, stix); if (isValidResolution) { - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } @@ -689,7 +707,7 @@ const createSseMiddleware = () => { const message = generateCreateMessage(instance); const origin = { referer: EVENT_TYPE_INIT }; const eventData = { data: stixData, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); + await channel.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); cache.set(stixData.id, 'hit'); } } else { diff --git a/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts b/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts index 1b2f59b40232..e017d3cfedcf 100644 --- a/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts +++ b/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts @@ -25,7 +25,7 @@ export const getChatbotProxy = async (req: Express.Request, res: Express.Respons const isChatbotCGUAccepted: boolean = settings.filigran_chatbot_ai_cgu_status === CguStatus.Enabled; const license_pem = getEnterpriseEditionActivePem(settings.enterprise_license); const licenseInfo = getEnterpriseEditionInfo(settings); - const isLicenseValidated = licenseInfo.license_validated; + const isLicenseValidated = license_pem !== undefined && licenseInfo.license_validated; if (!isChatbotCGUAccepted || !isLicenseValidated) { logApp.error('Error in chatbot proxy', { cguStatus: settings.filigran_chatbot_ai_cgu_status, isLicenseValidated, chatbotUrl: XTM_ONE_CHATBOT_URL }); diff --git a/opencti-platform/opencti-graphql/src/http/httpRollingFeed.ts b/opencti-platform/opencti-graphql/src/http/httpRollingFeed.ts index 15a481f7a13b..a55de5054e13 100644 --- a/opencti-platform/opencti-graphql/src/http/httpRollingFeed.ts +++ b/opencti-platform/opencti-graphql/src/http/httpRollingFeed.ts @@ -85,7 +85,7 @@ export const buildCsvLines = (elements: any[], feed: BasicStoreEntityFeed): stri const initHttpRollingFeeds = (app: Express.Application) => { app.get(`${basePath}/feeds/:id`, async (req: Express.Request, res: Express.Response) => { - const { id } = req.params; + const { id } = req.params as { id: string }; res.set({ 'content-type': 'text/plain; charset=utf-8' }); try { const context = await createAuthenticatedContext(req, res, 'rolling_feeds'); diff --git a/opencti-platform/opencti-graphql/src/initialization.js b/opencti-platform/opencti-graphql/src/initialization.js index 249ca2562a43..0ab9a40c0546 100644 --- a/opencti-platform/opencti-graphql/src/initialization.js +++ b/opencti-platform/opencti-graphql/src/initialization.js @@ -67,7 +67,7 @@ const refreshMappingsAndIndices = async () => { const initializeMigration = async (context) => { logApp.info('[INIT] Creating migration structure'); - const time = lastAvailableMigrationTime(); + const time = await lastAvailableMigrationTime(); const lastRun = `${time}-init`; const migrationStatus = { internal_id: uuidv4(), lastRun }; await createEntity(context, SYSTEM_USER, migrationStatus, ENTITY_TYPE_MIGRATION_STATUS); diff --git a/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts b/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts index db0eed211192..d95ac6ce0d54 100644 --- a/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts +++ b/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts @@ -147,9 +147,10 @@ export interface UserModificationAction extends BasicUserAction { export interface UserLoginAction extends BasicUserAction { event_type: 'authentication'; event_scope: 'login'; + session_kill?: number; context_data: { provider: string; - username: string; + username?: string; }; } export interface UserLogoutAction extends BasicUserAction { @@ -186,7 +187,6 @@ export const registerUserActionListener = (listener: ActionListener): ActionHand export const publishUserAction = async (userAction: UserAction) => { const actionPromises = []; - // eslint-disable-next-line no-restricted-syntax for (const [, listener] of listeners.entries()) { actionPromises.push(listener.next(userAction)); } diff --git a/opencti-platform/opencti-graphql/src/manager/activityListener.ts b/opencti-platform/opencti-graphql/src/manager/activityListener.ts index 896a5ebb8f98..c4730277f4fc 100644 --- a/opencti-platform/opencti-graphql/src/manager/activityListener.ts +++ b/opencti-platform/opencti-graphql/src/manager/activityListener.ts @@ -136,10 +136,11 @@ const initActivityManager = () => { // 02. Handle activities if (action.event_type === 'authentication') { if (action.event_scope === 'login') { - const { provider, username } = action.context_data; + const { session_kill, context_data } = action; + const { provider, username } = context_data; const isFailLogin = action.status === 'error'; const message = isFailLogin ? `detects \`login failure\` for \`${username}\`` - : `login from provider \`${provider}\``; + : `login from provider \`${provider}\` ${(session_kill ?? 0) > 0 ? `(killing ${session_kill} sessions)` : ''}`; await activityLogger(action, message); } if (action.event_scope === 'logout') { diff --git a/opencti-platform/opencti-graphql/src/manager/cacheManager.ts b/opencti-platform/opencti-graphql/src/manager/cacheManager.ts index bc59bf09ae34..c7a76ae769dd 100644 --- a/opencti-platform/opencti-graphql/src/manager/cacheManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/cacheManager.ts @@ -25,7 +25,7 @@ import { ENTITY_TYPE_USER, } from '../schema/internalObject'; import { RELATION_MEMBER_OF, RELATION_PARTICIPATE_TO } from '../schema/internalRelationship'; -import { ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; +import { ENTITY_TYPE_LABEL, ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; import type { BasicStoreSettings } from '../types/settings'; import type { StixObject } from '../types/stix-2-1-common'; import { STIX_EXT_OCTI } from '../types/stix-2-1-extensions'; @@ -145,15 +145,20 @@ const platformResolvedFilters = (context: AuthContext) => { return new Map(); }; const refreshFilter = async (values: Map, instance: BasicStoreCommon) => { - const filteringIds = extractResolvedFiltersFromInstance(instance); // Resolve filters ids that are not already in the cache const currentFiltersValues = values; // current cache map const idsToSolve: string[] = []; // will contain the ids to resolve that are not already in the cache - filteringIds.forEach((id) => { - if (!currentFiltersValues.has(id)) { - idsToSolve.push(id); - } - }); + // If a label or marking was updated, we always need to update it in the cache + if (instance.entity_type === ENTITY_TYPE_LABEL || instance.entity_type === ENTITY_TYPE_MARKING_DEFINITION) { + idsToSolve.push(instance.internal_id); + } else { + const filteringIds = extractResolvedFiltersFromInstance(instance); + filteringIds.forEach((id) => { + if (!currentFiltersValues.has(id)) { + idsToSolve.push(id); + } + }); + } const loadedDependencies = await stixLoadByIds(context, SYSTEM_USER, R.uniq(idsToSolve)); // fetch the stix instance of the ids // Add resolved stix entities to the cache map loadedDependencies.forEach((l: StixObject) => currentFiltersValues.set(l.extensions[STIX_EXT_OCTI].id, l)); diff --git a/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts b/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts index 77280bb74f3f..7f7e4603dc96 100644 --- a/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts @@ -150,7 +150,7 @@ const initFileIndexManager = () => { const fileIndexHandler = async () => { const context = executionContext(FILE_INDEX_MANAGER_NAME); const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS); - if (settings.valid_enterprise_edition === true) { + if (settings && settings.valid_enterprise_edition === true) { let lock; try { // Lock the manager diff --git a/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts b/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts index 9b2866aa68dd..27a13931ff07 100644 --- a/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts +++ b/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts @@ -14,7 +14,7 @@ import { isStixMatchFilterGroup } from '../../utils/filtering/filtering-stix/sti import { isEventCreateRelationship, isEventInPirRelationship, isEventUpdateOnEntity, isValidEventType } from './playbookManagerUtils'; import { STIX_SPEC_VERSION } from '../../database/stix'; import { playbookExecutor } from './playbookExecutor'; -import { PIR_SCORE_FILTER } from '../../utils/filtering/filtering-constants'; +import { PIR_IDS_SUBFILTER, PIR_SCORE_FILTER, PIR_SCORE_SUBFILTER } from '../../utils/filtering/filtering-constants'; import { STIX_EXT_OCTI } from '../../types/stix-2-1-extensions'; /** @@ -138,8 +138,8 @@ export const formatFiltersForPirPlaybookComponent = (sourceFilters: string, inPi return { key: [PIR_SCORE_FILTER], values: [ - { ...filter, key: 'score' }, - { key: 'pir_ids', values: (inPirFilters ?? []).map((pir) => pir.value) }, + { ...filter, key: PIR_SCORE_SUBFILTER }, + { key: PIR_IDS_SUBFILTER, values: (inPirFilters ?? []).map((pir) => pir.value) }, ], }; } @@ -174,7 +174,7 @@ export const listenPirEvents = async ( AUTOMATION_MANAGER_USER, data.source_ref, ABSTRACT_STIX_CORE_OBJECT, - ) as unknown as StixObject; ; + ) as unknown as StixObject; } else if (isUpdateEvent) { // Event update on flagged entity. stixEntity = data; @@ -185,7 +185,7 @@ export const listenPirEvents = async ( AUTOMATION_MANAGER_USER, stixIdLinked, ABSTRACT_STIX_CORE_OBJECT, - ) as unknown as StixObject; ; + ) as unknown as StixObject; } // Having an entity means we have a matched PIR. diff --git a/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts b/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts index 9c120a902971..e6ec57939c5d 100644 --- a/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts @@ -26,7 +26,7 @@ import { AUTOMATION_MANAGER_USER, executionContext, RETENTION_MANAGER_USER, SYST import type { SseEvent, StreamDataEvent } from '../../types/event'; import type { StixBundle, StixObject } from '../../types/stix-2-1-common'; import { streamEventId, utcDate } from '../../utils/format'; -import { findById } from '../../modules/playbook/playbook-domain'; +import { findById, findPlaybooksForEntity } from '../../modules/playbook/playbook-domain'; import { type CronConfiguration, PLAYBOOK_INTERNAL_DATA_CRON, type StreamConfiguration } from '../../modules/playbook/playbook-components'; import { PLAYBOOK_COMPONENTS } from '../../modules/playbook/playbook-components'; import type { BasicStoreEntityPlaybook, ComponentDefinition } from '../../modules/playbook/playbook-types'; @@ -132,15 +132,17 @@ const playbookStreamHandler = async (streamEvents: Array { - const playbooks = await getEntitiesListFromCache(context, SYSTEM_USER, ENTITY_TYPE_PLAYBOOK); + // fetch playbooks allowed for this entity + const playbooks = await findPlaybooksForEntity(context, RETENTION_MANAGER_USER, entityId); let playbook = null; + // keep the playbook corresponding to the id const filteredPlaybooks = playbooks.filter((n) => n.id === id); if (filteredPlaybooks.length > 0) { playbook = filteredPlaybooks.at(0); } else { - throw FunctionalError('Playbook does not exist', { id }); + throw FunctionalError('Playbook does not exist for this entity', { id }); } - // Execute only of definition is available + // Execute only if definition is available if (playbook && playbook.playbook_definition) { const def = JSON.parse(playbook.playbook_definition) as ComponentDefinition; const instance = def.nodes.find((n) => n.id === playbook.playbook_start); diff --git a/opencti-platform/opencti-graphql/src/migrations/1765807866114-add-vocabulary-ssh-key-type.js b/opencti-platform/opencti-graphql/src/migrations/1765807866114-add-vocabulary-ssh-key-type.js index 256ce0e2d0bc..6ec1447493d5 100644 --- a/opencti-platform/opencti-graphql/src/migrations/1765807866114-add-vocabulary-ssh-key-type.js +++ b/opencti-platform/opencti-graphql/src/migrations/1765807866114-add-vocabulary-ssh-key-type.js @@ -1,12 +1,12 @@ import { executionContext, SYSTEM_USER } from '../utils/access'; -import { logApp } from '../config/conf'; +import { logMigration } from '../config/conf'; import { VocabularyCategory } from '../generated/graphql'; import { builtInOv, openVocabularies } from '../modules/vocabulary/vocabulary-utils'; import { addVocabulary } from '../modules/vocabulary/vocabulary-domain'; const message = '[MIGRATION] Vocabulary add key_type_ov'; export const up = async (next) => { - logApp.info(`${message} > started`); + logMigration.info(`${message} > started`); const context = executionContext('migration'); const category = VocabularyCategory.KeyTypeOv; const vocabularies = openVocabularies[category] ?? []; @@ -20,7 +20,7 @@ export const up = async (next) => { }; await addVocabulary(context, SYSTEM_USER, data); } - logApp.info(`${message} > done. ${vocabularies.length} vocabularies added.`); + logMigration.info(`${message} > done. ${vocabularies.length} vocabularies added.`); next(); }; diff --git a/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql b/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql index 3eb8f307720a..cf76bab7e482 100644 --- a/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql +++ b/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql @@ -2,7 +2,7 @@ type AdministrativeArea implements BasicObject & StixCoreObject & StixDomainObje id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -198,6 +198,10 @@ input AdministrativeAreaAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts b/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts index ea984e30b18a..627d6080cc42 100644 --- a/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts +++ b/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts @@ -239,6 +239,7 @@ const internalObjectsAttributes: { [k: string]: Array } = { { name: 'xtm_hub_registration_status', label: 'XTM Hub registration status', type: 'string', format: 'enum', values: ['registered', 'unregistered', 'lost_connectivity'], editDefault: false, mandatoryType: 'no', multiple: false, upsert: false, isFilterable: false }, { name: 'filigran_chatbot_ai_cgu_status', label: 'XTM1 CGU acceptance status', type: 'string', format: 'enum', values: ['pending', 'disabled', 'enabled'], editDefault: false, mandatoryType: 'no', multiple: false, upsert: false, isFilterable: false }, { name: 'platform_ai_enabled', label: 'AI insight activation', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, + { name: 'platform_session_max_concurrent', label: 'Max concurrent sessions (0 equals no maximum)', type: 'numeric', precision: 'integer', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, ], [ENTITY_TYPE_MIGRATION_STATUS]: [ { name: 'lastRun', label: 'Last run', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, @@ -272,7 +273,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { }, { name: 'default_dashboard', label: 'Default dashboard', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: true, isFilterable: true }, { name: 'default_hidden_types', label: 'Default hidden types', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: true, upsert: false, isFilterable: true }, - { name: 'max_shareable_markings', + { + name: 'max_shareable_markings', label: 'Max shareable markings', type: 'object', format: 'standard', @@ -286,7 +288,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { { name: 'value', label: 'Marking', type: 'string', format: 'id', entityTypes: [ENTITY_TYPE_MARKING_DEFINITION], editDefault: false, mandatoryType: 'no', multiple: false, upsert: true, isFilterable: true }, ], }, - { name: 'group_confidence_level', + { + name: 'group_confidence_level', label: 'Group Confidence Level', type: 'object', format: 'standard', @@ -298,7 +301,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { sortBy: { path: 'group_confidence_level.max_confidence', type: 'numeric' }, mappings: [ { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'internal', multiple: false, upsert: false, isFilterable: true }, - { name: 'overrides', + { + name: 'overrides', label: 'Overrides', type: 'object', format: 'nested', @@ -310,7 +314,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { mappings: [ { name: 'entity_type', label: 'Entity Type', type: 'string', format: 'short', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, - ] }, + ], + }, ], }, { name: 'auto_integration_assignation', label: 'Default Group used for integration user creation', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: true, upsert: true, isFilterable: true }, @@ -357,7 +362,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { { name: 'submenu_show_icons', label: 'Show submenu icons', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'submenu_auto_collapse', label: 'Auto collapse submenus', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'monochrome_labels', label: 'Monochrome labels and entity types', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, - { name: 'user_confidence_level', + { + name: 'user_confidence_level', label: 'User Confidence Level', type: 'object', format: 'standard', @@ -368,7 +374,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { isFilterable: false, mappings: [ { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'no', multiple: false, upsert: false, isFilterable: true }, - { name: 'overrides', + { + name: 'overrides', label: 'Overrides', type: 'object', format: 'nested', @@ -380,7 +387,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { mappings: [ { name: 'entity_type', label: 'Entity Type', type: 'string', format: 'short', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, - ] }, + ], + }, ], }, { name: 'user_service_account', label: 'User service account', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: true }, diff --git a/opencti-platform/opencti-graphql/src/modules/auth/auth-domain.ts b/opencti-platform/opencti-graphql/src/modules/auth/auth-domain.ts index dd6f8f5fd3af..a0b4e9dce063 100644 --- a/opencti-platform/opencti-graphql/src/modules/auth/auth-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/auth/auth-domain.ts @@ -1,4 +1,3 @@ -import { authenticator } from 'otplib'; import bcrypt from 'bcryptjs'; import { v4 as uuid } from 'uuid'; import { findById, getUserByEmail, userEditField } from '../../domain/user'; @@ -19,6 +18,7 @@ import type { SendMailArgs } from '../../types/smtp'; import { addForgotPasswordCount } from '../../manager/telemetryManager'; import { sanitizeSettings } from '../../utils/templateContextSanitizer'; import { safeRender } from '../../utils/safeEjs.client'; +import { totp } from '../../utils/totp'; export const getLocalProviderUser = async (email: string) => { const user: any = await getUserByEmail(email); @@ -148,13 +148,13 @@ export const verifyMfa = async (context: AuthContext, input: VerifyMfaInput) => if (!mfa_activated || !mfa_secret) { throw AuthenticationFailure(); } - const isValidated = authenticator.check(input.code, mfa_secret); - if (!isValidated) { + const { valid } = await totp.verify({ secret: mfa_secret, token: input.code }); + if (!valid) { throw AuthenticationFailure(); } else { - await redisSetForgotPasswordOtp(input.transactionId, { hashedOtp, email, mfa_activated, mfa_validated: isValidated, userId }, ttl); + await redisSetForgotPasswordOtp(input.transactionId, { hashedOtp, email, mfa_activated, mfa_validated: valid, userId }, ttl); } - return isValidated; + return valid; }; export const changePassword = async (context: AuthContext, input: ChangePasswordInput) => { diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql index de1d56aaad77..14f5a5b8aa50 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql @@ -2,7 +2,7 @@ type CaseIncident implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -246,9 +246,13 @@ input CaseIncidentAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String update: Boolean authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql index 1c2e02e69f7a..0412689e517b 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql @@ -7,7 +7,7 @@ type CaseRfi implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -250,12 +250,16 @@ input CaseRfiAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String update: Boolean information_types: [String!] caseTemplates: [String!] authorized_members: [MemberAccessInput!] x_opencti_request_access: String + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql index 10c637895625..7d34bda867be 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql @@ -2,7 +2,7 @@ type CaseRft implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -241,6 +241,9 @@ input CaseRftAddInput { created: DateTime modified: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String x_opencti_modified_at: DateTime x_opencti_workflow_id: String @@ -248,6 +251,7 @@ input CaseRftAddInput { takedown_types: [String!] caseTemplates: [String!] authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql index 70f29c76721f..59916e3eab24 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql @@ -2,7 +2,7 @@ type CaseTemplate implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/case/case.graphql b/opencti-platform/opencti-graphql/src/modules/case/case.graphql index 9c8011284d6d..9b6825ea9c40 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case.graphql @@ -2,7 +2,7 @@ interface Case implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql b/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql index 59bcc69c8d38..1762d0216a1a 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql @@ -2,7 +2,7 @@ type Feedback implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -236,9 +236,13 @@ input FeedbackAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] clientMutationId: String update: Boolean rating: Int + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql b/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql index 8c95842f5e68..10c25d501fea 100644 --- a/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql +++ b/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql @@ -2,7 +2,7 @@ type Channel implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -194,6 +194,10 @@ input ChannelAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql b/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql index f050b63314d0..a2a738d65bcf 100644 --- a/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql +++ b/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql @@ -3,7 +3,7 @@ type DataComponent implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Stix Object representative: Representative! @@ -192,8 +192,12 @@ input DataComponentAddInput { aliases: [String] dataSource: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] x_opencti_workflow_id: String x_opencti_modified_at: DateTime + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql b/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql index c4718335e05c..3316c5d42a25 100644 --- a/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql +++ b/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql @@ -3,7 +3,7 @@ type DataSource implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Stix Object representative: Representative! @@ -194,8 +194,12 @@ input DataSourceAddInput { collection_layers: [String!] dataComponents: [String] file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] x_opencti_workflow_id: String x_opencti_modified_at: DateTime + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql b/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql index cb9e1121557b..92260397d8a9 100644 --- a/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql +++ b/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql @@ -2,7 +2,7 @@ type DecayRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! updated_at: DateTime! diff --git a/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql b/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql index 4b4c90047565..255789941672 100644 --- a/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql +++ b/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql @@ -2,7 +2,7 @@ type DecayExclusionRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! # DecayExclusionRule diff --git a/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql b/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql index 1ba7a9de7835..3de9407263a2 100644 --- a/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql +++ b/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql @@ -2,7 +2,7 @@ type DisseminationList implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! updated_at: DateTime! diff --git a/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql b/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql index e9a0efa0c749..5be5218bd066 100644 --- a/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql +++ b/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql @@ -2,7 +2,7 @@ type EmailTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # EmailTemplate name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/event/event.graphql b/opencti-platform/opencti-graphql/src/modules/event/event.graphql index 194bbf99897f..76dea7a3df8e 100644 --- a/opencti-platform/opencti-graphql/src/modules/event/event.graphql +++ b/opencti-platform/opencti-graphql/src/modules/event/event.graphql @@ -2,7 +2,7 @@ type Event implements BasicObject & StixCoreObject & StixDomainObject & StixObje id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -198,6 +198,10 @@ input EventAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql index 13f413499dcf..35d4b7f0402e 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql @@ -4,7 +4,7 @@ type ExclusionList implements InternalObject & BasicObject { description: String standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! enabled: Boolean! diff --git a/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql b/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql index b0548b143434..0245e9fc098e 100644 --- a/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql +++ b/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql @@ -2,7 +2,7 @@ type FintelDesign implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Fintel Design name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql b/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql index 83db07e6c2ab..1d5b1f1d41b5 100644 --- a/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql +++ b/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql @@ -7,7 +7,7 @@ type FintelTemplate implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] ## Fintel name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/form/form-domain.ts b/opencti-platform/opencti-graphql/src/modules/form/form-domain.ts index cfc252beaf5a..d38f9ae5f573 100644 --- a/opencti-platform/opencti-graphql/src/modules/form/form-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/form/form-domain.ts @@ -25,7 +25,7 @@ import { ENTITY_TYPE_MALWARE, isStixDomainObject, isStixDomainObjectContainer } import type { StixRelation } from '../../types/stix-2-1-sro'; import type { StixContainer } from '../../types/stix-2-1-sdo'; import { ENTITY_TYPE_CONTAINER_GROUPING } from '../grouping/grouping-types'; -import { detectObservableType } from '../../utils/observable'; +import { detectObservableType, refangValues } from '../../utils/observable'; import { createStixPattern } from '../../python/pythonBridge'; import pjson from '../../../package.json'; import { extractContentFrom } from '../../utils/fileToContent'; @@ -584,12 +584,14 @@ export const formSubmit = async ( mainEntityStixId = mainEntity.standard_id; } } else if (schema.mainEntityMultiple && schema.mainEntityFieldMode === 'parsed') { - for (let index = 0; index < values.mainEntityParsed.length; index += 1) { + // Refang (de-sanitize) parsed values to handle defanged IOCs + const refangedMainEntityParsed = refangValues(values.mainEntityParsed); + for (let index = 0; index < refangedMainEntityParsed.length; index += 1) { let mainEntity = { entity_type: mainEntityType } as StoreEntity; if (schema.mainEntityParseFieldMapping === 'pattern' && schema.mainEntityAutoConvertToStixPattern) { - // Auto convert the value - const observableType = detectObservableType(values.mainEntityParsed[index]); - const observableValue = values.mainEntityParsed[index]; + // Auto convert the value (using refanged value) + const observableValue = refangedMainEntityParsed[index]; + const observableType = detectObservableType(observableValue); const pattern = await createStixPattern(context, user, observableType, observableValue); mainEntity[schema.mainEntityParseFieldMapping] = pattern; mainEntity.pattern_type = 'stix'; @@ -598,7 +600,7 @@ export const formSubmit = async ( } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - mainEntity[schema.mainEntityParseFieldMapping] = values.mainEntityParsed[index]; + mainEntity[schema.mainEntityParseFieldMapping] = refangedMainEntityParsed[index]; } // Apply additional fields to all parsed entities @@ -724,12 +726,14 @@ export const formSubmit = async ( } } else if (additionalEntity.multiple && additionalEntity.fieldMode === 'parsed') { if (isNotEmptyField(values[`additional_${additionalEntity.id}_parsed`])) { - for (let index2 = 0; index2 < values[`additional_${additionalEntity.id}_parsed`].length; index2 += 1) { + // Refang (de-sanitize) parsed values to handle defanged IOCs + const refangedAdditionalParsed = refangValues(values[`additional_${additionalEntity.id}_parsed`]); + for (let index2 = 0; index2 < refangedAdditionalParsed.length; index2 += 1) { let newAdditionalEntity = { entity_type: additionalEntityType } as StoreEntity; if (additionalEntity.parseFieldMapping === 'pattern' && additionalEntity.autoConvertToStixPattern) { - // Auto convert the value - const observableType = detectObservableType(values[`additional_${additionalEntity.id}_parsed`][index2]); - const observableValue = values[`additional_${additionalEntity.id}_parsed`][index2]; + // Auto convert the value (using refanged value) + const observableValue = refangedAdditionalParsed[index2]; + const observableType = detectObservableType(observableValue); const pattern = await createStixPattern(context, user, observableType, observableValue); newAdditionalEntity[additionalEntity.parseFieldMapping] = pattern; newAdditionalEntity.pattern_type = 'stix'; @@ -738,7 +742,7 @@ export const formSubmit = async ( } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - newAdditionalEntity[additionalEntity.parseFieldMapping] = values[`additional_${additionalEntity.id}_parsed`][index2]; + newAdditionalEntity[additionalEntity.parseFieldMapping] = refangedAdditionalParsed[index2]; } // Apply additional fields to all parsed entities diff --git a/opencti-platform/opencti-graphql/src/modules/form/form-types.ts b/opencti-platform/opencti-graphql/src/modules/form/form-types.ts index 72e05203c7ff..985012d144e4 100644 --- a/opencti-platform/opencti-graphql/src/modules/form/form-types.ts +++ b/opencti-platform/opencti-graphql/src/modules/form/form-types.ts @@ -57,7 +57,7 @@ export interface FormFieldDefinition { required: boolean; isMandatory?: boolean; // Whether this field is for a mandatory attribute width?: 'full' | 'half' | 'third'; // Field width in grid: full (12), half (6), third (4) - multiple?: boolean; // For openvocab and select/multiselect fields + multiple?: boolean; // For openvocab, select/multiselect, and files fields (defaults to false for files) attributeMapping: { entity: string; // Entity ID this field maps to (main_entity or additional entity ID) attributeName: string; // The attribute name on that entity diff --git a/opencti-platform/opencti-graphql/src/modules/form/form.graphql b/opencti-platform/opencti-graphql/src/modules/form/form.graphql index 1cd73349b2b1..ff6b0faca2b6 100644 --- a/opencti-platform/opencti-graphql/src/modules/form/form.graphql +++ b/opencti-platform/opencti-graphql/src/modules/form/form.graphql @@ -27,6 +27,7 @@ type FormConnection { } enum FormsOrdering { + _score name created_at updated_at diff --git a/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts b/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts index 780957e35771..d75c8e4c0368 100644 --- a/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts @@ -10,11 +10,12 @@ import { type EntityOptions, internalLoadById, pageEntitiesConnection, storeLoad import { isStixId } from '../../schema/schemaUtils'; import { RELATION_CREATED_BY, RELATION_OBJECT } from '../../schema/stixRefRelationship'; import { elCount } from '../../database/engine'; -import { READ_INDEX_STIX_DOMAIN_OBJECTS } from '../../database/utils'; +import { isEmptyField, READ_INDEX_STIX_DOMAIN_OBJECTS } from '../../database/utils'; import type { DomainFindById } from '../../domain/domainTypes'; import { addFilter } from '../../utils/filtering/filtering-utils'; import { type BasicStoreEntityGrouping, ENTITY_TYPE_CONTAINER_GROUPING, type GroupingNumberResult } from './grouping-types'; +import { now } from '../../utils/format'; export const findById: DomainFindById = (context: AuthContext, user: AuthUser, groupingId: string) => { return storeLoadById(context, user, groupingId, ENTITY_TYPE_CONTAINER_GROUPING); @@ -25,7 +26,8 @@ export const findGroupingPaginated = (context: AuthContext, user: AuthUser, opts }; export const addGrouping = async (context: AuthContext, user: AuthUser, grouping: GroupingAddInput) => { - const created = await createEntity(context, user, grouping, ENTITY_TYPE_CONTAINER_GROUPING); + const groupingToCreate = isEmptyField(grouping.created) ? { ...grouping, created: now() } : grouping; + const created = await createEntity(context, user, groupingToCreate, ENTITY_TYPE_CONTAINER_GROUPING); return notify(BUS_TOPICS[ABSTRACT_STIX_DOMAIN_OBJECT].ADDED_TOPIC, created, user); }; diff --git a/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql b/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql index 914b7bf6eb6c..8f70b17ee1bb 100644 --- a/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql +++ b/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql @@ -3,7 +3,7 @@ type Grouping implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -270,7 +270,11 @@ input GroupingAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] authorized_members: [MemberAccessInput!] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/index.ts b/opencti-platform/opencti-graphql/src/modules/index.ts index 575f7771fa82..706abe9f0e91 100644 --- a/opencti-platform/opencti-graphql/src/modules/index.ts +++ b/opencti-platform/opencti-graphql/src/modules/index.ts @@ -150,4 +150,5 @@ import './emailTemplate/emailTemplate-graphql'; import './form/form-graphql'; import './xtm/hub/xtm-hub-graphql'; import './metrics/metrics-graphql'; +import './migration/migration-graphql'; // endregion diff --git a/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql b/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql index 3149198dee55..533b84f234f1 100644 --- a/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql +++ b/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql @@ -69,7 +69,7 @@ type Indicator implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -251,7 +251,11 @@ input IndicatorAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] basedOn: [String!] + upsertOperations: [EditInput!] } type Query { diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql index bd51c3de9a31..9cc5bab7ed48 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql @@ -3,7 +3,7 @@ type IngestionCsv implements InternalObject & BasicObject { id: ID! entity_type: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime updated_at: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql index c2e15e2f08e7..56620da28940 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql @@ -18,7 +18,7 @@ type IngestionJson implements InternalObject & BasicObject { entity_type: String! connector_id: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime updated_at: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts index c1892f891ff2..6658a8c4afa8 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts @@ -1,14 +1,20 @@ import { type BasicStoreEntityIngestionTaxii, ENTITY_TYPE_INGESTION_TAXII } from './ingestion-types'; import { createEntity, deleteElementById, patchAttribute, updateAttribute } from '../../database/middleware'; import { fullEntitiesList, pageEntitiesConnection, storeLoadById } from '../../database/middleware-loader'; -import { BUS_TOPICS } from '../../config/conf'; +import { BUS_TOPICS, PLATFORM_VERSION } from '../../config/conf'; import { publishUserAction } from '../../listener/UserActionListener'; import { notify } from '../../database/redis'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; import type { AuthContext, AuthUser } from '../../types/user'; -import { type EditInput, type IngestionTaxiiAddInput } from '../../generated/graphql'; +import { type EditInput, type IngestionTaxiiAddAutoUserInput, type IngestionTaxiiAddInput } from '../../generated/graphql'; import { addAuthenticationCredentials, removeAuthenticationCredentials, verifyIngestionAuthenticationContent } from './ingestion-common'; import { registerConnectorForIngestion, unregisterConnectorForIngestion } from '../../domain/connector'; +import { createOnTheFlyUser } from '../user/user-domain'; +import type { FileHandle } from 'fs/promises'; +import { extractContentFrom } from '../../utils/fileToContent'; +import { isCompatibleVersionWithMinimal } from '../../utils/version'; +import { FunctionalError } from '../../config/errors'; +const MINIMAL_TAXII_FEED_COMPATIBLE_VERSION = '6.9.4'; export const findById = async (context: AuthContext, user: AuthUser, ingestionId: string, removeCredentials = false) => { const taxiiIngestion = await storeLoadById(context, user, ingestionId, ENTITY_TYPE_INGESTION_TAXII); @@ -27,10 +33,20 @@ export const findAllTaxiiIngestion = async (context: AuthContext, user: AuthUser }; export const addIngestion = async (context: AuthContext, user: AuthUser, input: IngestionTaxiiAddInput) => { + if (input.automatic_user) { + const onTheFlyCreatedUser = await createOnTheFlyUser( + context, + user, + { userName: input.user_id, serviceAccount: true, confidenceLevel: input.confidence_level }, + ); + input = { ...input, user_id: onTheFlyCreatedUser.id }; + } if (input.authentication_value) { verifyIngestionAuthenticationContent(input.authentication_type, input.authentication_value); } - const { element, isCreation } = await createEntity(context, user, input, ENTITY_TYPE_INGESTION_TAXII, { complete: true }); + + const { automatic_user: _automatic_user, confidence_level: _confidence_level, ...taxiiFeedToCreate } = input; + const { element, isCreation } = await createEntity(context, user, taxiiFeedToCreate, ENTITY_TYPE_INGESTION_TAXII, { complete: true }); if (isCreation) { await registerConnectorForIngestion(context, { id: element.id, @@ -164,3 +180,49 @@ export const ingestionTaxiiResetState = async (context: AuthContext, user: AuthU }); return notify(BUS_TOPICS[ABSTRACT_INTERNAL_OBJECT].EDIT_TOPIC, ingestionUpdated, user); }; + +export const ingestionTaxiiAddAutoUser = async (context: AuthContext, user: AuthUser, ingestionId: string, input: IngestionTaxiiAddAutoUserInput) => { + const onTheFlyCreatedUser = await createOnTheFlyUser(context, user, + { userName: input.user_name, confidenceLevel: input.confidence_level, serviceAccount: true }); + + return ingestionEditField(context, user, ingestionId, [{ key: 'user_id', value: [onTheFlyCreatedUser.id] }]); +}; + +export const taxiiFeedAddInputFromImport = async (file: Promise) => { + const parsedData = await extractContentFrom(file); + + // check platform version compatibility + if (!isCompatibleVersionWithMinimal(parsedData.openCTI_version, MINIMAL_TAXII_FEED_COMPATIBLE_VERSION)) { + throw FunctionalError( + `Invalid version of the platform. Please upgrade your OpenCTI. Minimal version required: ${MINIMAL_TAXII_FEED_COMPATIBLE_VERSION}`, + { reason: parsedData.openCTI_version }, + ); + } + + return parsedData.configuration; +}; + +export const taxiiFeedExport = async (ingestionTaxii: BasicStoreEntityIngestionTaxii) => { + const { + name, + description, + uri, + version, + collection, + authentication_type, + added_after_start, + } = ingestionTaxii; + return JSON.stringify({ + openCTI_version: PLATFORM_VERSION, + type: 'taxiiFeeds', + configuration: { + name, + description, + uri, + version, + collection, + authentication_type, + added_after_start, + }, + }); +}; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts index 33c374b8fdb5..9ab9c83b3183 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts @@ -1,13 +1,25 @@ -import { addIngestion, findTaxiiIngestionPaginated, findById, ingestionDelete, ingestionEditField, ingestionTaxiiResetState } from './ingestion-taxii-domain'; +import { + addIngestion, + findTaxiiIngestionPaginated, + findById, + ingestionDelete, + ingestionEditField, + ingestionTaxiiResetState, + ingestionTaxiiAddAutoUser, + taxiiFeedAddInputFromImport, + taxiiFeedExport, +} from './ingestion-taxii-domain'; import type { Resolvers } from '../../generated/graphql'; const ingestionTaxiiResolvers: Resolvers = { Query: { ingestionTaxii: (_, { id }, context) => findById(context, context.user, id, true), ingestionTaxiis: (_, args, context) => findTaxiiIngestionPaginated(context, context.user, args), + taxiiFeedAddInputFromImport: (_, { file }) => taxiiFeedAddInputFromImport(file), }, IngestionTaxii: { user: (ingestionTaxii, _, context) => context.batch.creatorBatchLoader.load(ingestionTaxii.user_id), + toConfigurationExport: (ingestionTaxii) => taxiiFeedExport(ingestionTaxii), }, Mutation: { ingestionTaxiiAdd: (_, { input }, context) => { @@ -22,6 +34,9 @@ const ingestionTaxiiResolvers: Resolvers = { ingestionTaxiiFieldPatch: (_, { id, input }, context) => { return ingestionEditField(context, context.user, id, input); }, + ingestionTaxiiAddAutoUser: (_, { id, input }, context) => { + return ingestionTaxiiAddAutoUser(context, context.user, id, input); + }, }, }; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql index 5a635f83ebdc..284d9003661a 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql @@ -34,6 +34,7 @@ type IngestionTaxii implements InternalObject & BasicObject { ingestion_running: Boolean last_execution_date: DateTime confidence_to_score: Boolean + toConfigurationExport: String! } enum IngestionTaxiiOrdering { name @@ -52,6 +53,17 @@ type IngestionTaxiiEdge { node: IngestionTaxii! } +type TaxiiFeedAddInputFromImport { + name: String! + description: String! + uri: String! + version: String! + collection: String! + authentication_type: String! + authentication_value: String! + added_after_start: String +} + # Queries type Query { ingestionTaxii(id: String!): IngestionTaxii @auth(for: [INGESTION]) @@ -64,6 +76,9 @@ type Query { includeAuthorities: Boolean search: String ): IngestionTaxiiConnection @auth(for: [INGESTION]) + taxiiFeedAddInputFromImport( + file: Upload! + ): TaxiiFeedAddInputFromImport! @auth(for: [INGESTION]) } # Mutations @@ -78,7 +93,14 @@ input IngestionTaxiiAddInput { added_after_start: DateTime ingestion_running: Boolean confidence_to_score: Boolean - user_id: String + user_id: String! + automatic_user: Boolean + confidence_level: Int +} + +input IngestionTaxiiAddAutoUserInput { + user_name: String! + confidence_level: Int! } type Mutation { @@ -86,4 +108,5 @@ type Mutation { ingestionTaxiiDelete(id: ID!): ID @auth(for: [INGESTION_SETINGESTIONS]) ingestionTaxiiResetState(id: ID!): IngestionTaxii @auth(for: [INGESTION_SETINGESTIONS]) ingestionTaxiiFieldPatch(id: ID!, input: [EditInput!]!): IngestionTaxii @auth(for: [INGESTION_SETINGESTIONS]) + ingestionTaxiiAddAutoUser(id: ID!, input: IngestionTaxiiAddAutoUserInput!): IngestionTaxii @auth(for: [INGESTION_SETINGESTIONS]) } diff --git a/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql b/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql index a297d4dea838..0bda9ce45d47 100644 --- a/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql +++ b/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql @@ -65,7 +65,7 @@ type CsvMapper implements InternalObject & BasicObject { id: ID! entity_type: String! @auth standard_id: String! @auth - parent_types: [String!]! + parent_types: [String!]! @auth metrics:[Metric] @auth # CsvMapper name: String! @auth diff --git a/opencti-platform/opencti-graphql/src/modules/language/language.graphql b/opencti-platform/opencti-graphql/src/modules/language/language.graphql index 4cbac3dc18bc..8c05b6bdfe03 100644 --- a/opencti-platform/opencti-graphql/src/modules/language/language.graphql +++ b/opencti-platform/opencti-graphql/src/modules/language/language.graphql @@ -2,7 +2,7 @@ type Language implements BasicObject & StixCoreObject & StixDomainObject & StixO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -187,6 +187,10 @@ input LanguageAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql b/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql index dd1d333f221a..352a6c4cddd7 100644 --- a/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql +++ b/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql @@ -3,7 +3,7 @@ type MalwareAnalysis implements BasicObject & StixCoreObject & StixDomainObject id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -220,6 +220,10 @@ input MalwareAnalysisAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/migration/migration-domain.ts b/opencti-platform/opencti-graphql/src/modules/migration/migration-domain.ts new file mode 100644 index 000000000000..bba5ac8d498b --- /dev/null +++ b/opencti-platform/opencti-graphql/src/modules/migration/migration-domain.ts @@ -0,0 +1,35 @@ +import type { AuthContext, AuthUser } from '../../types/user'; +import { retrieveMigration } from '../../database/migration'; +import { logApp } from '../../config/conf'; +import { isBypassUser, SYSTEM_USER } from '../../utils/access'; +import { ForbiddenAccess, FunctionalError } from '../../config/errors'; +import { createEntity, createRelation, loadEntity } from '../../database/middleware'; +import { ENTITY_TYPE_MIGRATION_REFERENCE, ENTITY_TYPE_MIGRATION_STATUS } from '../../schema/internalObject'; +import { RELATION_MIGRATES } from '../../schema/internalRelationship'; + +export const runMigration = async (context: AuthContext, user: AuthUser, migrationFileName: string) => { + if (!isBypassUser(user)) { + throw ForbiddenAccess(); + } + // 01. Run the migration + const migration = await retrieveMigration(migrationFileName); + try { + migration.up(() => {}); + } catch (migrationError) { + logApp.error(`[MIGRATION] Migration ${migrationFileName} up error`, { cause: migrationError }); + throw FunctionalError('Error running migration', { cause: migrationError }); + } + logApp.info(`[MIGRATION] Migration ${migrationFileName} successfully run`); + + // 02. Indicate the migration is run + const migrationStatus = await loadEntity(context, SYSTEM_USER, [ENTITY_TYPE_MIGRATION_STATUS]); + // Crete the migration reference + const migrationRefInput = { title: migration.title, timestamp: migration.timestamp }; + const migrationRef = await createEntity(context, SYSTEM_USER, migrationRefInput, ENTITY_TYPE_MIGRATION_REFERENCE); + // Attach the reference to the migration status + const migrationRel = { fromId: migrationStatus?.id, toId: migrationRef.id, relationship_type: RELATION_MIGRATES }; + await createRelation(context, SYSTEM_USER, migrationRel); + logApp.info(`[MIGRATION] Migration ${migrationFileName} saved`); + + return true; +}; diff --git a/opencti-platform/opencti-graphql/src/modules/migration/migration-graphql.ts b/opencti-platform/opencti-graphql/src/modules/migration/migration-graphql.ts new file mode 100644 index 000000000000..88f8ae825fcb --- /dev/null +++ b/opencti-platform/opencti-graphql/src/modules/migration/migration-graphql.ts @@ -0,0 +1,8 @@ +import { registerGraphqlSchema } from '../../graphql/schema'; +import migrationDefs from '../migration/migration.graphql'; +import migrationResolvers from './migration-resolver'; + +registerGraphqlSchema({ + schema: migrationDefs, + resolver: migrationResolvers, +}); diff --git a/opencti-platform/opencti-graphql/src/modules/migration/migration-resolver.ts b/opencti-platform/opencti-graphql/src/modules/migration/migration-resolver.ts new file mode 100644 index 000000000000..36234d174891 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/modules/migration/migration-resolver.ts @@ -0,0 +1,10 @@ +import type { Resolvers } from '../../generated/graphql'; +import { runMigration } from './migration-domain'; + +const migrationResolvers: Resolvers = { + Mutation: { + runMigration: (_, { migrationName }, context) => runMigration(context, context.user, migrationName), + }, +}; + +export default migrationResolvers; diff --git a/opencti-platform/opencti-graphql/src/modules/migration/migration.graphql b/opencti-platform/opencti-graphql/src/modules/migration/migration.graphql new file mode 100644 index 000000000000..c377c7790ea5 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/modules/migration/migration.graphql @@ -0,0 +1,3 @@ +type Mutation { + runMigration(migrationName: String!): Boolean! @auth(for: [BYPASS]) +} diff --git a/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql b/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql index d19c22aa4e5d..5fa22da5bbe0 100644 --- a/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql +++ b/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql @@ -2,7 +2,7 @@ type Narrative implements BasicObject & StixCoreObject & StixDomainObject & Stix id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -194,8 +194,12 @@ input NarrativeAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] x_opencti_modified_at: DateTime x_opencti_workflow_id: String + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql b/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql index a98366477919..dba374d79e29 100644 --- a/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql +++ b/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql @@ -53,7 +53,7 @@ type Trigger implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime @@ -136,7 +136,7 @@ type Notification implements InternalObject & BasicObject { created: DateTime name: String! notification_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] notification_content: [NotificationContent!]! is_read: Boolean! diff --git a/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts b/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts index e3faf5affbcd..9b6d6e5bc1a1 100644 --- a/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts +++ b/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts @@ -1,9 +1,10 @@ import type { JSONSchemaType } from 'ajv'; import type { NotifierConnector } from '../../generated/graphql'; -import type { BasicStoreEntityNotifier } from './notifier-types'; +import { type BasicStoreEntityNotifier, ENTITY_TYPE_NOTIFIER } from './notifier-types'; import { HEADER_TEMPLATE } from '../../utils/emailTemplates/header'; import { FOOTER_TEMPLATE } from '../../utils/emailTemplates/footer'; import { LOGO_TEMPLATE } from '../../utils/emailTemplates/logo'; +import { ABSTRACT_BASIC_OBJECT, ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; // region Notifier User interface export const NOTIFIER_CONNECTOR_UI = 'f39b8ab2c-8f5c-4167-a249-229f34d9442b'; @@ -159,16 +160,23 @@ export const STATIC_NOTIFIERS: Array = [ // @ts-ignore { id: STATIC_NOTIFIER_UI, + standard_id: `notifier--${STATIC_NOTIFIER_UI}`, + entity_type: ENTITY_TYPE_NOTIFIER, + parent_types: [ABSTRACT_BASIC_OBJECT, ABSTRACT_INTERNAL_OBJECT], internal_id: STATIC_NOTIFIER_UI, built_in: true, name: 'User interface', description: 'Publish notification to the user interface', notifier_connector_id: NOTIFIER_CONNECTOR_UI, + notifier_configuration: '', }, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { id: STATIC_NOTIFIER_EMAIL, + standard_id: `notifier--${STATIC_NOTIFIER_EMAIL}`, + entity_type: ENTITY_TYPE_NOTIFIER, + parent_types: [ABSTRACT_BASIC_OBJECT, ABSTRACT_INTERNAL_OBJECT], internal_id: STATIC_NOTIFIER_EMAIL, built_in: true, name: 'Default mailer', diff --git a/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql b/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql index e2d0489a02af..cbc547cb9e24 100644 --- a/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql +++ b/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql @@ -23,7 +23,7 @@ type Notifier implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql b/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql index 2bfd6f01cc2b..2d8b065d3526 100644 --- a/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql +++ b/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql @@ -2,7 +2,7 @@ type Organization implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -231,6 +231,10 @@ input OrganizationAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql b/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql index 0b4c24e8c9ce..3e803f390c48 100644 --- a/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql +++ b/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql @@ -65,7 +65,7 @@ type PirRelationship implements BasicRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts index ea7bfcea4073..7dc1ec13b634 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts @@ -17,7 +17,15 @@ import { v4 as uuidv4 } from 'uuid'; import type { JSONSchemaType } from 'ajv'; import * as jsonpatch from 'fast-json-patch'; import { type BasicStoreEntityPlaybook, ENTITY_TYPE_PLAYBOOK, type PlaybookComponent } from './playbook-types'; -import { AUTOMATION_MANAGER_USER, AUTOMATION_MANAGER_USER_UUID, executionContext, isUserCanAccessStixElement, isUserInPlatformOrganization, SYSTEM_USER } from '../../utils/access'; +import { + type AuthorizedMember, + AUTOMATION_MANAGER_USER, + AUTOMATION_MANAGER_USER_UUID, + executionContext, + isUserCanAccessStixElement, + isUserInPlatformOrganization, + SYSTEM_USER, +} from '../../utils/access'; import { pushToConnector, pushToWorkerForConnector } from '../../database/rabbitmq'; import { ABSTRACT_STIX_CORE_OBJECT, @@ -28,7 +36,9 @@ import { ENTITY_TYPE_CONTAINER, ENTITY_TYPE_THREAT_ACTOR, INPUT_ASSIGNEE, + INPUT_AUTHORIZED_MEMBERS, INPUT_CREATED_BY, + INPUT_GRANTED_REFS, INPUT_KILLCHAIN, INPUT_LABELS, INPUT_MARKINGS, @@ -54,7 +64,7 @@ import { import type { CyberObjectExtension, StixBundle, StixCoreObject, StixCyberObject, StixDomainObject, StixObject, StixOpenctiExtension } from '../../types/stix-2-1-common'; import { STIX_EXT_MITRE, STIX_EXT_OCTI, STIX_EXT_OCTI_SCO } from '../../types/stix-2-1-extensions'; import { connectorsForPlaybook } from '../../database/repository'; -import { internalFindByIds, fullEntitiesList, fullRelationsList, storeLoadById } from '../../database/middleware-loader'; +import { fullEntitiesList, fullRelationsList, internalFindByIds, storeLoadById } from '../../database/middleware-loader'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../organization/organization-types'; import { getEntitiesMapFromCache, getEntityFromCache } from '../../database/cache'; import { createdBy, objectLabel, objectMarking } from '../../schema/stixRefRelationship'; @@ -89,13 +99,12 @@ import { ENTITY_TYPE_CONTAINER_CASE } from '../case/case-types'; import { findAllByCaseTemplateId } from '../task/task-domain'; import type { BasicStoreEntityTaskTemplate } from '../task/task-template/task-template-types'; import type { BasicStoreSettings } from '../../types/settings'; -import { AUTHORIZED_MEMBERS_SUPPORTED_ENTITY_TYPES, editAuthorizedMembers } from '../../utils/authorizedMembers'; -import { removeOrganizationRestriction } from '../../domain/stix'; +import { AUTHORIZED_MEMBERS_SUPPORTED_ENTITY_TYPES, buildRestrictedMembers } from '../../utils/authorizedMembers'; import { ENTITY_TYPE_CONTAINER_GROUPING } from '../grouping/grouping-types'; import { ENTITY_TYPE_CONTAINER_FEEDBACK } from '../case/feedback/feedback-types'; import { PLAYBOOK_SEND_EMAIL_TEMPLATE_COMPONENT } from './components/send-email-template-component'; +import { applyOperationFieldPatch, convertMembersToUsers, extractBundleBaseElement } from './playbook-utils'; import { PLAYBOOK_DATA_STREAM_PIR } from './components/data-stream-pir-component'; -import { convertMembersToUsers, extractBundleBaseElement } from './playbook-utils'; import { convertStoreToStix_2_1 } from '../../database/stix-2-1-converter'; import { ENTITY_TYPE_SECURITY_COVERAGE, INPUT_COVERED, type StixSecurityCoverage, type StoreEntitySecurityCoverage } from '../securityCoverage/securityCoverage-types'; @@ -367,7 +376,7 @@ const extendsBundleElementsWithExtensions = (bundle: StixBundle): StixBundle => }); return newBundle; }; -const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = { +export const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = { id: 'PLAYBOOK_CONNECTOR_COMPONENT', name: 'Enrich through connector', description: 'Use a registered platform connector for enrichment', @@ -384,8 +393,7 @@ const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = const schemaElement = { properties: { connector: { oneOf: elements } } }; return R.mergeDeepRight, any>(PLAYBOOK_CONNECTOR_COMPONENT_SCHEMA, schemaElement); }, - notify: async ({ executionId, eventId, playbookId, playbookNode, - previousPlaybookNodeId, dataInstanceId, bundle }) => { + notify: async ({ executionId, eventId, playbookId, playbookNode, previousPlaybookNodeId, dataInstanceId, bundle }) => { if (playbookNode.configuration.connector) { const baseData = extractBundleBaseElement(dataInstanceId, bundle); const message = { @@ -411,17 +419,42 @@ const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = await pushToConnector(playbookNode.configuration.connector, message); } }, - executor: async ({ bundle }) => { + executor: async ({ bundle, previousStepBundle }) => { // Add extensions if needed // This is needed as the rest of playbook expecting STIX2.1 format with extensions const stixBundle = extendsBundleElementsWithExtensions(bundle); - // TODO Could be reactivated after improvement of enrichment connectors - // if (previousStepBundle) { - // const diffOperations = jsonpatch.compare(previousStepBundle.objects, bundle.objects); - // if (diffOperations.length === 0) { - // return { output_port: 'unmodified', bundle }; - // } - // } + const resolveDuplicate = (a: any, b: any) => { + if (Array.isArray(a) && Array.isArray(b)) { + return R.uniq([...a, ...b]); + } + return b; + }; + if (previousStepBundle) { + const previousObjectsIndex: Record = {}; + previousStepBundle.objects.forEach((obj) => { + previousObjectsIndex[obj.id] = obj; + }); + // Check if new bundle objects has the same object ids of previous bundle objects + const enrichedObjects = stixBundle.objects.map((newObj) => { + const prevObj = previousObjectsIndex[newObj.id]; + if (prevObj) { + // Merge both objects if same ids + return R.mergeDeepWith(resolveDuplicate, prevObj, newObj); + } + return newObj; + }); + + // Check if new bundle contains objects of previous bundle and add them if not in it + const existingIds = new Set(stixBundle.objects.map((o) => o.id)); + const missingObjects = previousStepBundle.objects.filter( + (prevObj) => !existingIds.has(prevObj.id), + ); + if (missingObjects.length > 0) { + enrichedObjects.push(...missingObjects); + } + stixBundle.objects = enrichedObjects; + return { output_port: 'out', bundle: stixBundle }; + } return { output_port: 'out', bundle: stixBundle }; }, }; @@ -430,6 +463,8 @@ interface ContainerWrapperConfiguration { container_type: string; caseTemplates: { label: string; value: string }[]; all: boolean; + excludeMainElement: boolean; + copyFiles: boolean; newContainer: boolean; } const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_SCHEMA: JSONSchemaType = { @@ -444,6 +479,8 @@ const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_SCHEMA: JSONSchemaType { @@ -511,7 +548,7 @@ export const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT: PlaybookComponent, any>(PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_SCHEMA, schemaElement); }, executor: async ({ dataInstanceId, playbookNode, bundle }) => { - const { container_type, all, newContainer, caseTemplates } = playbookNode.configuration; + const { container_type, all, excludeMainElement, copyFiles, newContainer, caseTemplates } = playbookNode.configuration; if (!PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_AVAILABLE_CONTAINERS.includes(container_type)) { throw FunctionalError('this container type is incompatible with the Container Wrapper playbook component', { container_type }); } @@ -537,7 +574,6 @@ export const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT: PlaybookComponent o.id); + // If excludeMainElement is true and all is true, exclude the main element from the container + if (excludeMainElement) { + container.object_refs = bundle.objects.filter((o: StixObject) => o.id !== baseData.id).map((o: StixObject) => o.id); + } else { + container.object_refs = bundle.objects.map((o: StixObject) => o.id); + } } else { container.object_refs = [baseData.id]; } @@ -580,8 +621,12 @@ export const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT: PlaybookComponentbaseData).severity && container_type === ENTITY_TYPE_CONTAINER_CASE_INCIDENT) { (container).severity = (baseData).severity; } + // Copy files from the main element to the container if requested + if (copyFiles && baseData.extensions[STIX_EXT_OCTI].files && baseData.extensions[STIX_EXT_OCTI].files.length > 0) { + container.extensions[STIX_EXT_OCTI].files = baseData.extensions[STIX_EXT_OCTI].files; + } if (STIX_DOMAIN_OBJECT_CONTAINER_CASES.includes(container_type) && caseTemplates.length > 0) { - const tasks = await addTaskFromCaseTemplates(caseTemplates, (container as StixContainer)); + const tasks = await createTaskFromCaseTemplates(caseTemplates, (container as StixContainer)); bundle.objects.push(...tasks); } bundle.objects.push(container); @@ -792,13 +837,29 @@ export const PLAYBOOK_UNSHARING_COMPONENT: PlaybookComponent o.standard_id); + const patchOperations = []; for (let index = 0; index < bundle.objects.length; index += 1) { const element = bundle.objects[index]; if (all || element.id === dataInstanceId) { - for (let index2 = 0; index2 < organizationsValues.length; index2 += 1) { - await removeOrganizationRestriction(context, AUTOMATION_MANAGER_USER, element.extensions[STIX_EXT_OCTI].id, organizationsValues[index2]); - } - element.extensions[STIX_EXT_OCTI].granted_refs = (element.extensions[STIX_EXT_OCTI].granted_refs ?? []).filter((o) => !organizationIds.includes(o)); + const patchValue = { + op: EditOperation.Remove, + path: `/objects/${index}/extensions/${STIX_EXT_OCTI}/granted_refs`, + value: organizationIds, + }; + const patchOperation = { + operation: patchValue.op, + key: INPUT_GRANTED_REFS, + value: patchValue.value, + }; + applyOperationFieldPatch(element, [patchOperation]); + patchOperations.push(patchValue); + } + } + if (patchOperations.length > 0) { + const patchedBundle = jsonpatch.applyPatch(structuredClone(bundle), patchOperations).newDocument; + const diff = jsonpatch.compare(bundle, patchedBundle); + if (isNotEmptyField(diff)) { + return { output_port: 'out', bundle: patchedBundle }; } } return { output_port: 'out', bundle }; @@ -879,11 +940,15 @@ export const PLAYBOOK_ACCESS_RESTRICTIONS_COMPONENT: PlaybookComponent ({ id: n.value, access_right: n.accessRight, groups_restriction_ids: n.groupsRestriction.map((o) => o.value), })); + if (input.length === 0) { + return { output_port: 'out', bundle }; + } for (let index = 0; index < bundle.objects.length; index += 1) { const element = bundle.objects[index]; const internalType = generateInternalType(element); @@ -895,9 +960,26 @@ export const PLAYBOOK_ACCESS_RESTRICTIONS_COMPONENT: PlaybookComponent 0) { + const patchedBundle = jsonpatch.applyPatch(structuredClone(bundle), patchOperations).newDocument; + const diff = jsonpatch.compare(bundle, patchedBundle); + if (isNotEmptyField(diff)) { + return { output_port: 'out', bundle: patchedBundle }; } } return { output_port: 'out', bundle }; @@ -924,22 +1006,31 @@ export const PLAYBOOK_REMOVE_ACCESS_RESTRICTIONS_COMPONENT: PlaybookComponent PLAYBOOK_REMOVE_ACCESS_RESTRICTIONS_COMPONENT_SCHEMA, executor: async ({ dataInstanceId, playbookNode, bundle }) => { - const context = executionContext('playbook_components'); const { all } = playbookNode.configuration; + const patchOperations = []; for (let index = 0; index < bundle.objects.length; index += 1) { const element = bundle.objects[index]; const internalType = generateInternalType(element); if (AUTHORIZED_MEMBERS_SUPPORTED_ENTITY_TYPES.includes(internalType) && (all || element.id === dataInstanceId)) { - const args = { - entityId: element.id, - input: null, - requiredCapabilities: ['KNOWLEDGE_KNUPDATE_KNMANAGEAUTHMEMBERS'], - entityType: internalType, - busTopicKey: ABSTRACT_STIX_DOMAIN_OBJECT, + const patchValue = { + op: EditOperation.Replace, + path: `/objects/${index}/extensions/${STIX_EXT_OCTI}/restricted_members`, + value: [], }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - await editAuthorizedMembers(context, AUTOMATION_MANAGER_USER, args); + const patchOperation = { + operation: EditOperation.Replace, + key: INPUT_AUTHORIZED_MEMBERS, + value: [], + }; + applyOperationFieldPatch(element, [patchOperation]); + patchOperations.push(patchValue); + } + } + if (patchOperations.length > 0) { + const patchedBundle = jsonpatch.applyPatch(structuredClone(bundle), patchOperations).newDocument; + const diff = jsonpatch.compare(bundle, patchedBundle); + if (isNotEmptyField(diff)) { + return { output_port: 'out', bundle: patchedBundle }; } } return { output_port: 'out', bundle }; @@ -1039,7 +1130,7 @@ const PLAYBOOK_UPDATE_KNOWLEDGE_COMPONENT_SCHEMA: JSONSchemaType = { +export const PLAYBOOK_UPDATE_KNOWLEDGE_COMPONENT: PlaybookComponent = { id: 'PLAYBOOK_UPDATE_KNOWLEDGE_COMPONENT', name: 'Manipulate knowledge', description: 'Manipulate STIX data', @@ -1087,7 +1178,7 @@ const PLAYBOOK_UPDATE_KNOWLEDGE_COMPONENT: PlaybookComponent { const attrPath = computeAttributePath(type, action.attribute); @@ -1101,27 +1192,64 @@ const PLAYBOOK_UPDATE_KNOWLEDGE_COMPONENT: PlaybookComponent { if (multiple) { const currentValues = jsonpatch.getValueByPointer(bundle, path) ?? []; - const actionValues = action.value.map((o) => { + // the patch value can be the "label" instead of id (for ex: markings ids / labels ids) + const actionPatchValues = action.value.map((o) => { // If value is an id, must be converted to standard_id has we work on stix bundle if (cacheIds.has(o.patch_value)) return (cacheIds.get(o.patch_value) as BasicStoreCommon).standard_id; // Else, just return the value return convertValue(attributeType, o.patch_value); }); + // the value is always the id + const actionValues = action.value.map((o) => { + // If value is an id, must be converted to standard_id has we work on stix bundle + if (cacheIds.has(o.value)) return (cacheIds.get(o.value) as BasicStoreCommon).standard_id; + // Else, just return the value + return convertValue(attributeType, o.value); + }); if (action.op === EditOperation.Add) { - return { op: EditOperation.Replace, path, value: R.uniq([...currentValues, ...actionValues]) }; + return { + op: action.op, + attribute: action.attribute, + value: actionValues, + patchOperation: { op: EditOperation.Replace, path, value: R.uniq([...currentValues, ...actionPatchValues]) }, + }; } if (action.op === EditOperation.Replace) { - return { op: EditOperation.Replace, path, value: actionValues }; + return { + op: action.op, + attribute: action.attribute, + value: actionValues, + patchOperation: { op: EditOperation.Replace, path, value: actionPatchValues }, + }; } if (action.op === EditOperation.Remove) { - return { op: EditOperation.Replace, path, value: currentValues.filter((c: any) => !actionValues.includes(c)) }; + return { + op: action.op, + attribute: action.attribute, + value: actionValues, + patchOperation: { op: EditOperation.Replace, path, value: currentValues.filter((c: any) => !actionPatchValues.includes(c)) }, + }; } } - const currentValue = R.head(action.value)?.patch_value; - return { op: action.op, path, value: convertValue(attributeType, currentValue) }; + const currentPatchValue = R.head(action.value)?.patch_value; + const currentValue = R.head(action.value)?.value; + return { + op: action.op, + attribute: action.attribute, + value: currentValue, + patchOperation: { op: action.op, path, value: convertValue(attributeType, currentPatchValue) }, + }; }); // Enlist operations to execute - patchOperations.push(...elementOperations); + if (elementOperations.length > 0) { + const operationObject = elementOperations.map((op) => { + return { key: op.attribute, value: Array.isArray(op.value) ? op.value : [op.value], operation: op.op }; + }); + if (id) { + applyOperationFieldPatch(element, operationObject); + } + patchOperations.push(...elementOperations.map((e) => e.patchOperation)); + } } } // Apply operations if needed diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts index 5f915595d44a..e0c5bec19505 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts @@ -22,7 +22,7 @@ import { notify } from '../../database/redis'; import type { DomainFindById } from '../../domain/domainTypes'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; import type { AuthContext, AuthUser } from '../../types/user'; -import { type EditInput, FilterMode, type PlaybookAddInput, type PlaybookAddLinkInput, type PlaybookAddNodeInput, type PositionInput } from '../../generated/graphql'; +import { type EditInput, type PlaybookAddInput, type PlaybookAddLinkInput, type PlaybookAddNodeInput, type PositionInput } from '../../generated/graphql'; import type { BasicStoreEntityPlaybook, ComponentDefinition } from './playbook-types'; import { ENTITY_TYPE_PLAYBOOK } from './playbook-types'; import { PLAYBOOK_COMPONENTS, type SharingConfiguration, type StreamConfiguration } from './playbook-components'; @@ -32,7 +32,6 @@ import { isStixMatchFilterGroup } from '../../utils/filtering/filtering-stix/sti import { registerConnectorQueues, unregisterConnector } from '../../database/rabbitmq'; import { getEntitiesListFromCache } from '../../database/cache'; import { SYSTEM_USER } from '../../utils/access'; -import { findFiltersFromKey } from '../../utils/filtering/filtering-utils'; import { checkEnterpriseEdition, isEnterpriseEdition } from '../../enterprise-edition/ee'; import pjson from '../../../package.json'; import { extractContentFrom } from '../../utils/fileToContent'; @@ -72,12 +71,7 @@ export const findPlaybooksForEntity = async (context: AuthContext, user: AuthUse if (instance && (instance.component_id === 'PLAYBOOK_INTERNAL_DATA_STREAM' || instance.component_id === 'PLAYBOOK_INTERNAL_MANUAL_TRIGGER')) { const { filters } = JSON.parse(instance.configuration ?? '{}') as StreamConfiguration; const jsonFilters = filters ? JSON.parse(filters) : null; - const newFilters = { - mode: FilterMode.And, - filters: findFiltersFromKey(jsonFilters?.filters ?? [], 'entity_type'), - filterGroups: [], - }; - const isMatch = await isStixMatchFilterGroup(context, SYSTEM_USER, stixEntity, newFilters); + const isMatch = await isStixMatchFilterGroup(context, SYSTEM_USER, stixEntity, jsonFilters); if (isMatch) { filteredPlaybooks.push(playbook); } diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-utils.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-utils.ts index 2856576bc282..37149867dbe8 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-utils.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-utils.ts @@ -74,6 +74,17 @@ export const convertMembersToUsers = async ( return R.uniqBy(R.prop('id'), users); }; +export const applyOperationFieldPatch = (element: StixObject, patchObject: { + key: string; + value: any[]; + operation: 'add' | 'replace' | 'remove'; +}[]) => { + if (!element.extensions[STIX_EXT_OCTI].opencti_upsert_operations) { + element.extensions[STIX_EXT_OCTI].opencti_upsert_operations = []; + } + element.extensions[STIX_EXT_OCTI].opencti_upsert_operations.push(...patchObject); +}; + export const deleteLinksAndAllChildren = (definition: ComponentDefinition, links: LinkDefinition[]) => { // Resolve all nodes to delete const linksToDelete = links; @@ -91,7 +102,7 @@ export const deleteLinksAndAllChildren = (definition: ComponentDefinition, links childrenNodes = definition.nodes.filter((n) => linksToDelete.map((o) => o.to.id).includes(n.id) && !nodesToDelete.map((o) => o.id).includes(n.id)); if (childrenNodes.length > 0) { nodesToDelete.push(...childrenNodes); - // eslint-disable-next-line @typescript-eslint/no-loop-func + childrenLinks = definition.links.filter((n) => childrenNodes.map((o) => o.id).includes(n.from.id)); } else { childrenLinks = []; diff --git a/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql b/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql index 584c493c8ae0..1e2d8f986d64 100644 --- a/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql +++ b/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql @@ -2,7 +2,7 @@ type SavedFilter implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # SavedFilters name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql b/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql index d49c25a474db..d390add61467 100644 --- a/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql +++ b/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql @@ -7,7 +7,7 @@ type SecurityCoverage implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -215,6 +215,10 @@ input SecurityCoverageAddInput { x_opencti_modified_at: DateTime external_uri: String externalReferences: [String] + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] update: Boolean # coverage specific auto_enrichment_disable: Boolean! diff --git a/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql b/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql index eab71c4c4e60..d6a1eeaf4f9f 100644 --- a/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql +++ b/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql @@ -2,7 +2,7 @@ type SecurityPlatform implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -193,6 +193,11 @@ input SecurityPlatformAddInput { x_opencti_modified_at: DateTime externalReferences: [String] update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/settings/licensing.js b/opencti-platform/opencti-graphql/src/modules/settings/licensing.ts similarity index 68% rename from opencti-platform/opencti-graphql/src/modules/settings/licensing.js rename to opencti-platform/opencti-graphql/src/modules/settings/licensing.ts index 566d1061e48e..04b77c3f2cf6 100644 --- a/opencti-platform/opencti-graphql/src/modules/settings/licensing.js +++ b/opencti-platform/opencti-graphql/src/modules/settings/licensing.ts @@ -18,6 +18,8 @@ import { isNotEmptyField } from '../../database/utils'; import { now, utcDate } from '../../utils/format'; import { OPENCTI_CA } from '../../enterprise-edition/opencti_ca'; import conf, { PLATFORM_VERSION } from '../../config/conf'; +import type { BasicStoreSettings } from '../../types/settings'; +import type { PlatformEe } from '../../generated/graphql'; const GLOBAL_LICENSE_OPTION = 'global'; export const LICENSE_OPTION_TRIAL = 'trial'; @@ -26,31 +28,32 @@ export const IS_LTS_PLATFORM = PLATFORM_VERSION.includes('lts'); // https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers // 62944 - Filigran -export const LICENSE_OPTION_TYPE = '6.2.9.4.4.10'; -export const LICENSE_OPTION_PRODUCT = '6.2.9.4.4.20'; -export const LICENSE_OPTION_CREATOR = '6.2.9.4.4.30'; +export const LICENSE_OID_TYPE = '1.3.6.1.4.1.62944.10'; +export const LICENSE_OID_PRODUCT = '1.3.6.1.4.1.62944.20'; +export const LICENSE_OID_CREATOR = '1.3.6.1.4.1.62944.30'; +// Legacy OIDs +export const LICENSE_LEGACY_TYPE = '6.2.9.4.4.10'; +export const LICENSE_LEGACY_PRODUCT = '6.2.9.4.4.20'; +export const LICENSE_LEGACY_CREATOR = '6.2.9.4.4.30'; -const getExtensionValue = (clientCrt, extension) => { - return clientCrt.extensions.find((ext) => ext.id === extension)?.value; -}; - -export const getEnterpriseEditionActivePem = (rawPem) => { - const pemFromConfig = conf.get('app:enterprise_edition_license'); - return isNotEmptyField(pemFromConfig) ? pemFromConfig : rawPem; +const getExtensionValue = (clientCrt: forge.pki.Certificate, standardOid: string, legacyOid: string) => { + const extStandard = clientCrt.extensions.find((ext) => ext.id === standardOid); + if (extStandard) { + return extStandard.value; + } + return clientCrt.extensions.find((ext) => ext.id === legacyOid)?.value; }; -export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { - const pemFromConfig = conf.get('app:enterprise_edition_license'); - const pem = getEnterpriseEditionActivePem(rawPem); - const license_enterprise = isNotEmptyField(pem); - if (isNotEmptyField(pem)) { +const decodeLicense = (platformInstanceId: string, licenseByConfiguration: boolean, pem: string | undefined): PlatformEe => { + const license_enterprise = pem !== undefined && isNotEmptyField(pem); + if (license_enterprise) { try { const clientCrt = forge.pki.certificateFromPem(pem); const license_valid_cert = OPENCTI_CA.verify(clientCrt); - const license_type = getExtensionValue(clientCrt, LICENSE_OPTION_TYPE); + const license_type = getExtensionValue(clientCrt, LICENSE_OID_TYPE, LICENSE_LEGACY_TYPE); const valid_type = IS_LTS_PLATFORM ? license_type === LICENSE_OPTION_LTS : true; - const license_creator = getExtensionValue(clientCrt, LICENSE_OPTION_CREATOR); - const valid_product = getExtensionValue(clientCrt, LICENSE_OPTION_PRODUCT) === 'opencti'; + const license_creator = getExtensionValue(clientCrt, LICENSE_OID_CREATOR, LICENSE_LEGACY_CREATOR); + const valid_product = getExtensionValue(clientCrt, LICENSE_OID_PRODUCT, LICENSE_LEGACY_PRODUCT) === 'opencti'; const license_customer = clientCrt.subject.getField('O').value; const license_platform = clientCrt.subject.getField('OU').value; const license_platform_match = valid_product && valid_type && (license_platform === GLOBAL_LICENSE_OPTION || platformInstanceId === license_platform); @@ -66,7 +69,7 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { // If trial license, deactivation for expiration is direct if (license_type !== LICENSE_OPTION_TRIAL) { // If standard or lts license, a 3 months safe period is granted - const license_extra_expiration_date = utcDate(clientCrt.validity.notBefore).add(3, 'months'); + const license_extra_expiration_date = utcDate(clientCrt.validity.notAfter).add(3, 'months'); license_extra_expiration_days = license_extra_expiration_date.diff(utcDate(), 'days'); license_extra_expiration = new Date() < license_extra_expiration_date.toDate(); license_validated = license_extra_expiration; @@ -74,7 +77,7 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { } return { license_enterprise, // If EE activated - license_by_configuration: isNotEmptyField(pemFromConfig), + license_by_configuration: licenseByConfiguration, license_validated, // If EE license is ok (identifier, dates, ...) license_valid_cert, license_customer, @@ -97,7 +100,7 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { return { license_enterprise, license_validated: false, - license_by_configuration: isNotEmptyField(pemFromConfig), + license_by_configuration: licenseByConfiguration, license_valid_cert: false, license_extra_expiration: false, license_extra_expiration_days: 0, @@ -114,6 +117,25 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { }; }; -export const getEnterpriseEditionInfo = (settings) => { +export const getEnterpriseEditionActivePem = (rawPem: string | undefined) => { + const pemFromConfig: string | undefined = conf.get('app:enterprise_edition_license'); + return isNotEmptyField(pemFromConfig) ? pemFromConfig : rawPem; +}; + +let cachedLicence: PlatformEe | undefined = undefined; +let cachedPem: string | undefined = undefined; + +export const getEnterpriseEditionInfoFromPem = (platformInstanceId: string, rawPem: string | undefined) => { + const pem = getEnterpriseEditionActivePem(rawPem); + if (cachedLicence === undefined || cachedPem !== pem) { + const pemFromConfig = conf.get('app:enterprise_edition_license'); + const licenseByConfiguration = isNotEmptyField(pemFromConfig); + cachedLicence = decodeLicense(platformInstanceId, licenseByConfiguration, pem); + cachedPem = pem; + } + return cachedLicence; +}; + +export const getEnterpriseEditionInfo = (settings: BasicStoreSettings) => { return getEnterpriseEditionInfoFromPem(settings.internal_id, settings.enterprise_license); }; diff --git a/opencti-platform/opencti-graphql/src/modules/support/support.graphql b/opencti-platform/opencti-graphql/src/modules/support/support.graphql index f9f6dea6cad3..95d949d0972b 100644 --- a/opencti-platform/opencti-graphql/src/modules/support/support.graphql +++ b/opencti-platform/opencti-graphql/src/modules/support/support.graphql @@ -3,7 +3,7 @@ type SupportPackage implements InternalObject & BasicObject { name: String! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! package_status: PackageStatus! diff --git a/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql b/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql index c19bfc72bf1d..50511aacdf63 100644 --- a/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql +++ b/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql @@ -2,7 +2,7 @@ type TaskTemplate implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/task/task.graphql b/opencti-platform/opencti-graphql/src/modules/task/task.graphql index c3f95ed6e284..13d3e853409c 100644 --- a/opencti-platform/opencti-graphql/src/modules/task/task.graphql +++ b/opencti-platform/opencti-graphql/src/modules/task/task.graphql @@ -2,7 +2,7 @@ type Task implements Container & StixDomainObject & StixCoreObject & StixObject id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -225,6 +225,11 @@ input TaskAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql b/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql index 1082606cb64e..b640fd8c1ce1 100644 --- a/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql +++ b/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql @@ -12,7 +12,7 @@ type ThreatActorIndividual implements BasicObject & StixObject & StixCoreObject id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -230,6 +230,10 @@ input ThreatActorIndividualAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] + files: [Upload] + filesMarkings: [[String]] + upsertOperations: [EditInput!] } type Query { diff --git a/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql b/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql index 5ef7d977f138..aef8230e24b9 100644 --- a/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql +++ b/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql @@ -66,7 +66,7 @@ type Vocabulary implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts b/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts index f258dc84fc7e..4ec046a1007d 100644 --- a/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts +++ b/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-param-reassign */ import { DateTime } from 'luxon'; import type { AttributeDefinition, AttrType, ObjectAttribute } from '../schema/attribute-definition'; import { entityType, relationshipType, standardId } from '../schema/attribute-definition'; @@ -338,8 +337,11 @@ const mapRecord = async ( if (!isValidInput(filledInput)) { return null; } - - handleId(representation, filledInput); + try { + handleId(representation, filledInput); + } catch { + return null; + } return filledInput; }; diff --git a/opencti-platform/opencti-graphql/src/python/requirements.txt b/opencti-platform/opencti-graphql/src/python/requirements.txt index 513517814965..22b1c746a99d 100644 --- a/opencti-platform/opencti-graphql/src/python/requirements.txt +++ b/opencti-platform/opencti-graphql/src/python/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.3 +pycti==6.9.9 parsuricata==0.4.1 yara-python==4.5.2 sigmatools==0.23.1 diff --git a/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts b/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts index 8d61ff0b510f..913969ef17ef 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts +++ b/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts @@ -15,6 +15,9 @@ const basicObjectResolvers: Resolvers = { /* v8 ignore next */ return 'Unknown'; }, + parent_types(obj) { + return obj.parent_types.filter((t) => t); + }, metrics(obj) { return obj.metrics ? obj.metrics : []; }, diff --git a/opencti-platform/opencti-graphql/src/resolvers/campaign.js b/opencti-platform/opencti-graphql/src/resolvers/campaign.js index b793826af3b5..dfd02f85a667 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/campaign.js +++ b/opencti-platform/opencti-graphql/src/resolvers/campaign.js @@ -8,6 +8,7 @@ import { stixDomainObjectEditField, } from '../domain/stixDomainObject'; import { ENTITY_TYPE_CAMPAIGN } from '../schema/stixDomainObject'; +import { findSecurityCoverageByCoveredId } from '../modules/securityCoverage/securityCoverage-domain'; const campaignResolvers = { Query: { @@ -20,6 +21,9 @@ const campaignResolvers = { return campaignsTimeSeries(context, context.user, args); }, }, + Campaign: { + securityCoverage: (campaign, _, context) => findSecurityCoverageByCoveredId(context, context.user, campaign.id), + }, Mutation: { campaignEdit: (_, { id }, context) => ({ delete: () => stixDomainObjectDelete(context, context.user, id, ENTITY_TYPE_CAMPAIGN), diff --git a/opencti-platform/opencti-graphql/src/resolvers/connector.js b/opencti-platform/opencti-graphql/src/resolvers/connector.js index ef0459070f69..80c59750cbda 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/connector.js +++ b/opencti-platform/opencti-graphql/src/resolvers/connector.js @@ -25,10 +25,13 @@ import { syncDelete, syncEditContext, syncEditField, + synchronizerExport, + synchronizerAddAutoUser, testSync, updateConnectorCurrentStatus, updateConnectorManagerStatus, updateConnectorRequestedStatus, + syncAddInputFromImport, } from '../domain/connector'; import { addDraftContext, @@ -83,6 +86,7 @@ const connectorResolvers = { work: (_, { id }, context) => findById(context, context.user, id), isWorkAlive: (_, { id }, context) => isWorkAlive(context, context.user, id), synchronizer: (_, { id }, context) => findSyncById(context, context.user, id, true), + synchronizerAddInputFromImport: (_, { file }) => syncAddInputFromImport(file), synchronizers: (_, args, context) => findSyncPaginated(context, context.user, args), synchronizerFetch: (_, { input }, context) => fetchRemoteStreams(context, context.user, input), // region new managed connectors @@ -127,6 +131,7 @@ const connectorResolvers = { Synchronizer: { user: (sync, _, context) => context.batch.creatorBatchLoader.load(sync.user_id), queue_messages: async (sync, _, context) => getConnectorQueueSize(context, context.user, sync.id), + toConfigurationExport: (synchronizer) => synchronizerExport(synchronizer), }, Mutation: { deleteConnector: (_, { id }, context) => connectorDelete(context, context.user, id), @@ -177,6 +182,7 @@ const connectorResolvers = { workDelete: (_, { connectorId }, context) => deleteWorkForConnector(context, context.user, connectorId), // Sync part synchronizerAdd: (_, { input }, context) => registerSync(context, context.user, input), + synchronizerAddAutoUser: (_, { id, input }, context) => synchronizerAddAutoUser(context, context.user, id, input), synchronizerEdit: (_, { id }, context) => ({ delete: () => syncDelete(context, context.user, id), fieldPatch: ({ input }) => syncEditField(context, context.user, id, input), diff --git a/opencti-platform/opencti-graphql/src/resolvers/stix.js b/opencti-platform/opencti-graphql/src/resolvers/stix.js index 6b2c19f21ac7..ebccf07ed98d 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/stix.js +++ b/opencti-platform/opencti-graphql/src/resolvers/stix.js @@ -7,6 +7,7 @@ import { INPUT_GRANTED_REFS } from '../schema/general'; import { filterMembersWithUsersOrgs, isUserHasCapability, KNOWLEDGE_ORGANIZATION_RESTRICT, REDACTED_USER } from '../utils/access'; import { ENABLED_DEMO_MODE } from '../config/conf'; import { ENTITY_TYPE_USER } from '../schema/internalObject'; +import { FunctionalError } from '../config/errors'; const internalLoadThroughDenormalized = (context, user, element, inputName) => { if (inputName === INPUT_GRANTED_REFS) { @@ -24,6 +25,10 @@ const internalLoadThroughDenormalized = (context, user, element, inputName) => { } // If not, reload through denormalized relationships const ref = schemaRelationsRefDefinition.getRelationRef(element.entity_type, inputName); + // Some refs defined on API are not part of all entities in schema due to an API/schema misalignment + if (!ref) { + throw FunctionalError('No ref of this type is defined for this entity type in DB schema', { entity_type: element.entity_type, inputName }); + } return context.batch.relsBatchLoader.load({ element, definition: ref }); }; diff --git a/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js b/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js index 2b2747539b60..100da7d67f69 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js +++ b/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js @@ -1,4 +1,5 @@ import { findStixMetaObjectPaginated, findById } from '../domain/stixMetaObject'; +import { getSpecVersionOrDefault } from '../domain/stixRelationship'; const stixMetaObjectResolvers = { Query: { @@ -6,7 +7,7 @@ const stixMetaObjectResolvers = { stixMetaObjects: (_, args, context) => findStixMetaObjectPaginated(context, context.user, args), }, StixMetaObject: { - // eslint-disable-next-line + __resolveType(obj) { if (obj.entity_type) { return obj.entity_type.replace(/(?:^|-|_)(\w)/g, (matches, letter) => letter.toUpperCase()); @@ -14,6 +15,8 @@ const stixMetaObjectResolvers = { /* v8 ignore next */ return 'Unknown'; }, + // Retro compatibility + spec_version: getSpecVersionOrDefault, }, }; diff --git a/opencti-platform/opencti-graphql/src/rules/attribution-indicator-indicates/AttributionIndicatorIndicatesDefinition.ts b/opencti-platform/opencti-graphql/src/rules/attribution-indicator-indicates/AttributionIndicatorIndicatesDefinition.ts new file mode 100644 index 000000000000..242636061a14 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/attribution-indicator-indicates/AttributionIndicatorIndicatesDefinition.ts @@ -0,0 +1,51 @@ +import { RELATION_ATTRIBUTED_TO, RELATION_INDICATES } from '../../schema/stixCoreRelationship'; +import type { RuleDefinition } from '../../types/rules'; + +const id = 'attribution_indicator_indicates'; +const name = 'Indicator propagation via attribution'; +const description = 'If an entity is attributed to another entity and an indicator indicates the first entity, then the indicator also indicates the second entity.'; +const category = 'Parent-child propagation'; +const display = { + if: [ + { + source: 'Entity A', + source_color: '#ff9800', + relation: 'relationship_attributed-to', + target: 'Entity B', + target_color: '#4caf50', + }, + { + source: 'Indicator C', + source_color: '#00bcd4', + relation: 'relationship_indicates', + target: 'Entity A', + target_color: '#ff9800', + }, + ], + then: [ + { + action: 'CREATE', + relation: 'relationship_indicates', + source: 'Indicator C', + source_color: '#00bcd4', + target: 'Entity B', + target_color: '#4caf50', + }, + ], +}; + +// For rescan +const scan = { types: [RELATION_ATTRIBUTED_TO] }; + +// For live +const filters = { types: [RELATION_INDICATES, RELATION_ATTRIBUTED_TO] }; +const attributes = [ + { name: 'start_time' }, + { name: 'stop_time' }, + { name: 'confidence' }, + { name: 'object_marking_refs' }, +]; +const scopes = [{ filters, attributes }]; + +const definition: RuleDefinition = { id, name, description, scan, scopes, category, display }; +export default definition; diff --git a/opencti-platform/opencti-graphql/src/rules/attribution-indicator-indicates/AttributionIndicatorIndicatesRule.ts b/opencti-platform/opencti-graphql/src/rules/attribution-indicator-indicates/AttributionIndicatorIndicatesRule.ts new file mode 100644 index 000000000000..a396f693aeca --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/attribution-indicator-indicates/AttributionIndicatorIndicatesRule.ts @@ -0,0 +1,21 @@ +/* eslint-disable camelcase */ +import buildRelationToRelationRule from '../relationToRelationBuilder'; +import { RELATION_ATTRIBUTED_TO, RELATION_INDICATES } from '../../schema/stixCoreRelationship'; +import def from './AttributionIndicatorIndicatesDefinition'; + +/** + * Rule: If Entity A attributed to Entity B, If Indicator C indicates Entity A, + * Then Indicator C indicates Entity B + * + * Pattern: + * - C indicates A (leftType) + * - A attributed-to B (rightType) + * - Result: C indicates B (creationType) + */ +const AttributionIndicatorIndicatesRule = buildRelationToRelationRule(def, { + leftType: RELATION_INDICATES, + rightType: RELATION_ATTRIBUTED_TO, + creationType: RELATION_INDICATES, +}); + +export default AttributionIndicatorIndicatesRule; diff --git a/opencti-platform/opencti-graphql/src/rules/attribution-observable-related/AttributionObservableRelatedDefinition.ts b/opencti-platform/opencti-graphql/src/rules/attribution-observable-related/AttributionObservableRelatedDefinition.ts new file mode 100644 index 000000000000..7172e89f023a --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/attribution-observable-related/AttributionObservableRelatedDefinition.ts @@ -0,0 +1,51 @@ +import { RELATION_ATTRIBUTED_TO, RELATION_RELATED_TO } from '../../schema/stixCoreRelationship'; +import type { RuleDefinition } from '../../types/rules'; + +const id = 'attribution_observable_related'; +const name = 'Observable relation propagation via attribution'; +const description = 'If an entity is attributed to another entity and an observable is related to the first entity, then the observable is also related to the second entity.'; +const category = 'Parent-child propagation'; +const display = { + if: [ + { + source: 'Entity A', + source_color: '#ff9800', + relation: 'relationship_attributed-to', + target: 'Entity B', + target_color: '#4caf50', + }, + { + source: 'Observable C', + source_color: '#00bcd4', + relation: 'relationship_related-to', + target: 'Entity A', + target_color: '#ff9800', + }, + ], + then: [ + { + action: 'CREATE', + relation: 'relationship_related-to', + source: 'Observable C', + source_color: '#00bcd4', + target: 'Entity B', + target_color: '#4caf50', + }, + ], +}; + +// For rescan +const scan = { types: [RELATION_ATTRIBUTED_TO] }; + +// For live +const filters = { types: [RELATION_RELATED_TO, RELATION_ATTRIBUTED_TO] }; +const attributes = [ + { name: 'start_time' }, + { name: 'stop_time' }, + { name: 'confidence' }, + { name: 'object_marking_refs' }, +]; +const scopes = [{ filters, attributes }]; + +const definition: RuleDefinition = { id, name, description, scan, scopes, category, display }; +export default definition; diff --git a/opencti-platform/opencti-graphql/src/rules/attribution-observable-related/AttributionObservableRelatedRule.ts b/opencti-platform/opencti-graphql/src/rules/attribution-observable-related/AttributionObservableRelatedRule.ts new file mode 100644 index 000000000000..2f2425f2a2d4 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/attribution-observable-related/AttributionObservableRelatedRule.ts @@ -0,0 +1,21 @@ +/* eslint-disable camelcase */ +import buildRelationToRelationRule from '../relationToRelationBuilder'; +import { RELATION_ATTRIBUTED_TO, RELATION_RELATED_TO } from '../../schema/stixCoreRelationship'; +import def from './AttributionObservableRelatedDefinition'; + +/** + * Rule: If Entity A attributed to Entity B, If Observable C related-to Entity A, + * Then Observable C related-to Entity B + * + * Pattern: + * - C related-to A (leftType) + * - A attributed-to B (rightType) + * - Result: C related-to B (creationType) + */ +const AttributionObservableRelatedRule = buildRelationToRelationRule(def, { + leftType: RELATION_RELATED_TO, + rightType: RELATION_ATTRIBUTED_TO, + creationType: RELATION_RELATED_TO, +}); + +export default AttributionObservableRelatedRule; diff --git a/opencti-platform/opencti-graphql/src/rules/belongs-to-attributed/BelongsToAttributedDefinition.ts b/opencti-platform/opencti-graphql/src/rules/belongs-to-attributed/BelongsToAttributedDefinition.ts new file mode 100644 index 000000000000..a1f0eb457156 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/belongs-to-attributed/BelongsToAttributedDefinition.ts @@ -0,0 +1,51 @@ +import { RELATION_ATTRIBUTED_TO, RELATION_BELONGS_TO } from '../../schema/stixCoreRelationship'; +import type { RuleDefinition } from '../../types/rules'; + +const id = 'belongs_to_attributed'; +const name = 'Belongs-to propagation via attribution'; +const description = 'If an entity belongs to another entity and that entity is attributed to a third entity, then the first entity also belongs to the third entity.'; +const category = 'Parent-child propagation'; +const display = { + if: [ + { + source: 'Entity A', + source_color: '#ff9800', + relation: 'relationship_belongs-to', + target: 'Entity B', + target_color: '#4caf50', + }, + { + source: 'Entity B', + source_color: '#4caf50', + relation: 'relationship_attributed-to', + target: 'Entity C', + target_color: '#00bcd4', + }, + ], + then: [ + { + action: 'CREATE', + relation: 'relationship_belongs-to', + source: 'Entity A', + source_color: '#ff9800', + target: 'Entity C', + target_color: '#00bcd4', + }, + ], +}; + +// For rescan +const scan = { types: [RELATION_ATTRIBUTED_TO] }; + +// For live +const filters = { types: [RELATION_BELONGS_TO, RELATION_ATTRIBUTED_TO] }; +const attributes = [ + { name: 'start_time' }, + { name: 'stop_time' }, + { name: 'confidence' }, + { name: 'object_marking_refs' }, +]; +const scopes = [{ filters, attributes }]; + +const definition: RuleDefinition = { id, name, description, scan, scopes, category, display }; +export default definition; diff --git a/opencti-platform/opencti-graphql/src/rules/belongs-to-attributed/BelongsToAttributedRule.ts b/opencti-platform/opencti-graphql/src/rules/belongs-to-attributed/BelongsToAttributedRule.ts new file mode 100644 index 000000000000..a993402ba3cd --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/belongs-to-attributed/BelongsToAttributedRule.ts @@ -0,0 +1,21 @@ +/* eslint-disable camelcase */ +import buildRelationToRelationRule from '../relationToRelationBuilder'; +import { RELATION_ATTRIBUTED_TO, RELATION_BELONGS_TO } from '../../schema/stixCoreRelationship'; +import def from './BelongsToAttributedDefinition'; + +/** + * Rule: If Entity A belongs to Entity B, If Entity B attributed to Entity C, + * Then Entity A belongs to Entity C + * + * Pattern: + * - A belongs-to B (leftType) + * - B attributed-to C (rightType) + * - Result: A belongs-to C (creationType) + */ +const BelongsToAttributedRule = buildRelationToRelationRule(def, { + leftType: RELATION_BELONGS_TO, + rightType: RELATION_ATTRIBUTED_TO, + creationType: RELATION_BELONGS_TO, +}); + +export default BelongsToAttributedRule; diff --git a/opencti-platform/opencti-graphql/src/rules/infrastructure-observable-related/InfrastructureObservableRelatedDefinition.ts b/opencti-platform/opencti-graphql/src/rules/infrastructure-observable-related/InfrastructureObservableRelatedDefinition.ts new file mode 100644 index 000000000000..5e73828068b0 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/infrastructure-observable-related/InfrastructureObservableRelatedDefinition.ts @@ -0,0 +1,57 @@ +import { RELATION_CONSISTS_OF, RELATION_USES } from '../../schema/stixCoreRelationship'; +import { ABSTRACT_STIX_CYBER_OBSERVABLE } from '../../schema/general'; +import { ENTITY_TYPE_INFRASTRUCTURE } from '../../schema/stixDomainObject'; +import type { RuleDefinition } from '../../types/rules'; + +const id = 'infrastructure_observable_related'; +const name = 'Observable related to entity via infrastructure'; +const description = 'If an entity uses an infrastructure and the infrastructure consists of an observable, then the observable is related to the entity.'; +const category = 'Correlation'; +const display = { + if: [ + { + source: 'Entity A', + source_color: '#ff9800', + relation: 'relationship_uses', + target: 'Infrastructure B', + target_color: '#4caf50', + }, + { + source: 'Infrastructure B', + source_color: '#4caf50', + relation: 'relationship_consists-of', + target: 'Observable C', + target_color: '#00bcd4', + }, + ], + then: [ + { + action: 'CREATE', + relation: 'relationship_related-to', + source: 'Observable C', + source_color: '#00bcd4', + target: 'Entity A', + target_color: '#ff9800', + }, + ], +}; + +// For rescan +const scan = { + types: [RELATION_CONSISTS_OF], + fromTypes: [ENTITY_TYPE_INFRASTRUCTURE], + toTypes: [ABSTRACT_STIX_CYBER_OBSERVABLE], +}; + +// For live +const filters = { types: [RELATION_USES, RELATION_CONSISTS_OF] }; +const attributes = [ + { name: 'start_time' }, + { name: 'stop_time' }, + { name: 'confidence' }, + { name: 'object_marking_refs' }, +]; +const scopes = [{ filters, attributes }]; + +const definition: RuleDefinition = { id, name, description, scan, scopes, category, display }; +export default definition; diff --git a/opencti-platform/opencti-graphql/src/rules/infrastructure-observable-related/InfrastructureObservableRelatedRule.ts b/opencti-platform/opencti-graphql/src/rules/infrastructure-observable-related/InfrastructureObservableRelatedRule.ts new file mode 100644 index 000000000000..18adadb62b78 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/infrastructure-observable-related/InfrastructureObservableRelatedRule.ts @@ -0,0 +1,116 @@ +/* eslint-disable camelcase */ +import { createInferredRelation, deleteInferredRuleElement } from '../../database/middleware'; +import { buildPeriodFromDates, computeRangeIntersection } from '../../utils/format'; +import { RELATION_CONSISTS_OF, RELATION_RELATED_TO, RELATION_USES } from '../../schema/stixCoreRelationship'; +import def from './InfrastructureObservableRelatedDefinition'; +import { createRuleContent } from '../rules-utils'; +import { computeAverage } from '../../database/utils'; +import { fullRelationsList } from '../../database/middleware-loader'; +import type { StixRelation } from '../../types/stix-2-1-sro'; +import { STIX_EXT_OCTI } from '../../types/stix-2-1-extensions'; +import type { BasicStoreRelation, StoreObject } from '../../types/store'; +import { RELATION_OBJECT_MARKING } from '../../schema/stixRefRelationship'; +import { executionContext, RULE_MANAGER_USER } from '../../utils/access'; + +/** + * Rule: If Entity A uses Infrastructure B, If Infrastructure B consists of Observable C, + * Then Observable C related to Entity A + * + * Pattern: + * - A uses B (A -> B) + * - B consists-of C (B -> C) + * - Result: C related-to A (C -> A) + */ +const ruleInfrastructureObservableRelatedBuilder = () => { + // Execution + const applyUpsert = async (data: StixRelation): Promise => { + const context = executionContext(def.name, RULE_MANAGER_USER); + const { extensions } = data; + const createdId = extensions[STIX_EXT_OCTI].id; + const sourceRef = extensions[STIX_EXT_OCTI].source_ref; + const targetRef = extensions[STIX_EXT_OCTI].target_ref; + const { object_marking_refs: markings, relationship_type } = data; + const { confidence: createdConfidence, start_time: startTime, stop_time: stopTime } = data; + const creationRange = buildPeriodFromDates(startTime, stopTime); + + // Case 1: When "B consists-of C" is created + // Look for "uses" relations TO B (sourceRef), find A, then create "C related-to A" + if (relationship_type === RELATION_CONSISTS_OF) { + const listToCallback = async (relationships: Array) => { + const rels = relationships.filter((r) => r.internal_id !== createdId); + for (let relIndex = 0; relIndex < rels.length; relIndex += 1) { + const { internal_id: foundRelationId, fromId, confidence, start_time, stop_time } = rels[relIndex]; + const { [RELATION_OBJECT_MARKING]: object_marking_refs } = rels[relIndex]; + const existingRange = buildPeriodFromDates(start_time, stop_time); + const range = computeRangeIntersection(creationRange, existingRange); + const elementMarkings = [...(markings || []), ...(object_marking_refs || [])]; + const computedConfidence = computeAverage([createdConfidence, confidence]); + // Rule content + // Dependencies: Entity A (fromId), uses relation (foundRelationId), Infrastructure B (sourceRef), + // consists-of relation (createdId), Observable C (targetRef) + const dependencies = [fromId, foundRelationId, sourceRef, createdId, targetRef]; + const explanation = [foundRelationId, createdId]; + // Create the inferred relation: C related-to A + const ruleContent = createRuleContent(def.id, dependencies, explanation, { + confidence: computedConfidence, + start_time: range.start, + stop_time: range.end, + objectMarking: elementMarkings, + }); + const input = { fromId: targetRef, toId: fromId, relationship_type: RELATION_RELATED_TO }; + await createInferredRelation(context, input, ruleContent); + } + }; + // Look for "uses" relations TO sourceRef (Infrastructure B) + const listToArgs = { toId: sourceRef, callback: listToCallback }; + await fullRelationsList(context, RULE_MANAGER_USER, RELATION_USES, listToArgs); + } + + // Case 2: When "A uses B" is created + // Look for "consists-of" relations FROM B (targetRef), find C, then create "C related-to A" + if (relationship_type === RELATION_USES) { + const listFromCallback = async (relationships: Array) => { + const rels = relationships.filter((r) => r.internal_id !== createdId); + for (let relIndex = 0; relIndex < rels.length; relIndex += 1) { + const { internal_id: foundRelationId, toId, confidence, start_time, stop_time } = rels[relIndex]; + const { [RELATION_OBJECT_MARKING]: object_marking_refs } = rels[relIndex]; + const existingRange = buildPeriodFromDates(start_time, stop_time); + const range = computeRangeIntersection(creationRange, existingRange); + const elementMarkings = [...(markings || []), ...(object_marking_refs || [])]; + const computedConfidence = computeAverage([createdConfidence, confidence]); + // Rule content + // Dependencies: Entity A (sourceRef), uses relation (createdId), Infrastructure B (targetRef), + // consists-of relation (foundRelationId), Observable C (toId) + const dependencies = [sourceRef, createdId, targetRef, foundRelationId, toId]; + const explanation = [createdId, foundRelationId]; + // Create the inferred relation: C related-to A + const ruleContent = createRuleContent(def.id, dependencies, explanation, { + confidence: computedConfidence, + start_time: range.start, + stop_time: range.end, + objectMarking: elementMarkings, + }); + const input = { fromId: toId, toId: sourceRef, relationship_type: RELATION_RELATED_TO }; + await createInferredRelation(context, input, ruleContent); + } + }; + // Look for "consists-of" relations FROM targetRef (Infrastructure B) + const listFromArgs = { fromId: targetRef, callback: listFromCallback }; + await fullRelationsList(context, RULE_MANAGER_USER, RELATION_CONSISTS_OF, listFromArgs); + } + }; + // Contract + const clean = async (element: StoreObject, deletedDependencies: Array): Promise => { + await deleteInferredRuleElement(def.id, element, deletedDependencies); + }; + const insert = async (element: StixRelation): Promise => { + return applyUpsert(element); + }; + const update = async (element: StixRelation): Promise => { + return applyUpsert(element); + }; + return { ...def, insert, update, clean }; +}; +const RuleInfrastructureObservableRelated = ruleInfrastructureObservableRelatedBuilder(); + +export default RuleInfrastructureObservableRelated; diff --git a/opencti-platform/opencti-graphql/src/rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToDefinition.ts b/opencti-platform/opencti-graphql/src/rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToDefinition.ts new file mode 100644 index 000000000000..835f3a817bf5 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToDefinition.ts @@ -0,0 +1,65 @@ +import { RELATION_BELONGS_TO } from '../../schema/stixCoreRelationship'; +import { ABSTRACT_STIX_CYBER_OBSERVABLE } from '../../schema/general'; +import { ENTITY_TYPE_CONTAINER_REPORT } from '../../schema/stixDomainObject'; +import type { RuleDefinition, RuleFilters, RuleScope } from '../../types/rules'; + +const id = 'report_ref_observable_belongs_to'; +const name = 'Observables propagation in reports via belongs-to'; +const description = 'Propagate observables that an observable belongs to in a report.'; +const category = 'Report propagation'; +const display = { + if: [ + { + source: 'Report A', + source_color: '#ff9800', + relation: 'relationship_object', + target: 'Observable B', + target_color: '#4caf50', + }, + { + source: 'Observable B', + source_color: '#4caf50', + relation: 'relationship_belongs-to', + target: 'Observable C', + target_color: '#00bcd4', + identifier: 'Relation D', + identifier_color: '#673ab7', + }, + ], + then: [ + { + action: 'CREATE', + relation: 'relationship_object', + source: 'Report A', + source_color: '#ff9800', + target: 'Observable C', + target_color: '#00bcd4', + }, + { + action: 'CREATE', + relation: 'relationship_object', + source: 'Report A', + source_color: '#ff9800', + target: 'Relation D', + target_color: '#7e57c2', + }, + ], +}; + +// For rescan +const scan: RuleFilters = { types: [ENTITY_TYPE_CONTAINER_REPORT] }; + +// For live +const scopes: Array = [ + { + filters: { types: [ENTITY_TYPE_CONTAINER_REPORT] }, + attributes: [{ name: 'object_refs' }], + }, + { + filters: { types: [RELATION_BELONGS_TO], fromTypes: [ABSTRACT_STIX_CYBER_OBSERVABLE], toTypes: [ABSTRACT_STIX_CYBER_OBSERVABLE] }, + attributes: [], + }, +]; + +const definition: RuleDefinition = { id, name, description, scan, scopes, category, display }; +export default definition; diff --git a/opencti-platform/opencti-graphql/src/rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToRule.ts b/opencti-platform/opencti-graphql/src/rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToRule.ts new file mode 100644 index 000000000000..fce6327527cd --- /dev/null +++ b/opencti-platform/opencti-graphql/src/rules/report-refs-observable-belongs-to/ReportRefObservableBelongsToRule.ts @@ -0,0 +1,23 @@ +/* eslint-disable camelcase */ +import { ENTITY_TYPE_CONTAINER_REPORT } from '../../schema/stixDomainObject'; +import { ABSTRACT_STIX_CYBER_OBSERVABLE } from '../../schema/general'; +import def from './ReportRefObservableBelongsToDefinition'; +import buildContainerRefsRule from '../containerWithRefsBuilder'; +import { RELATION_BELONGS_TO } from '../../schema/stixCoreRelationship'; + +/** + * Rule: If Container A object-ref (contains) Observable B, If Observable B belongs to Observable C, + * Then Container A object-ref (contains) Observable C + * + * Pattern: + * - Report A contains Observable B (object_refs) + * - Observable B belongs-to Observable C + * - Result: Report A contains Observable C and the belongs-to relation + */ +const ReportRefObservableBelongsToRule = buildContainerRefsRule(def, ENTITY_TYPE_CONTAINER_REPORT, { + leftType: ABSTRACT_STIX_CYBER_OBSERVABLE, + rightType: ABSTRACT_STIX_CYBER_OBSERVABLE, + creationType: RELATION_BELONGS_TO, +}); + +export default ReportRefObservableBelongsToRule; diff --git a/opencti-platform/opencti-graphql/src/rules/rules-definition.ts b/opencti-platform/opencti-graphql/src/rules/rules-definition.ts index 893fd726b48d..1a1325ddbde0 100644 --- a/opencti-platform/opencti-graphql/src/rules/rules-definition.ts +++ b/opencti-platform/opencti-graphql/src/rules/rules-definition.ts @@ -1,8 +1,12 @@ // region static registration of rules definition import AttributedToAttributedDefinition from './attributed-to-attributed/AttributedToAttributedDefinition'; +import AttributionIndicatorIndicatesDefinition from './attribution-indicator-indicates/AttributionIndicatorIndicatesDefinition'; +import AttributionObservableRelatedDefinition from './attribution-observable-related/AttributionObservableRelatedDefinition'; import AttributionTargetsDefinition from './attribution-targets/AttributionTargetsDefinition'; import AttributionUseDefinition from './attribution-use/AttributionUseDefinition'; +import BelongsToAttributedDefinition from './belongs-to-attributed/BelongsToAttributedDefinition'; import IndicateSightedDefinition from './indicate-sighted/IndicateSightedDefinition'; +import InfrastructureObservableRelatedDefinition from './infrastructure-observable-related/InfrastructureObservableRelatedDefinition'; import LocalizationOfTargetsDefinition from './localization-of-targets/LocalizationOfTargetsDefinition'; import LocatedAtLocatedDefinition from './located-at-located/LocatedAtLocatedDefinition'; import LocationTargetsDefinition from './location-targets/LocationTargetsDefinition'; @@ -16,6 +20,7 @@ import ReportRefIdentityPartOfDefinition from './report-refs-identity-part-of/Re import ReportRefIndicatorBasedOnDefinition from './report-refs-indicator-based-on/ReportRefIndicatorBasedOnDefinition'; import ReportRefLocationLocatedAtDefinition from './report-refs-location-located-at/ReportRefLocationLocatedAtDefinition'; import ReportRefObservableBasedOnDefinition from './report-refs-observable-based-on/ReportRefObservableBasedOnDefinition'; +import ReportRefObservableBelongsToDefinition from './report-refs-observable-belongs-to/ReportRefObservableBelongsToDefinition'; import SightingIncidentDefinition from './sighting-incident/SightingIncidentDefinition'; import SightingIndicatorDefinition from './sighting-indicator/SightingIndicatorDefinition'; import SightingObservableDefinition from './sighting-observable/SightingObservableDefinition'; @@ -23,9 +28,13 @@ import ParentTechniqueUseDefinition from './parent-technique-use/ParentTechnique export const rule_definitions = [ AttributedToAttributedDefinition, + AttributionIndicatorIndicatesDefinition, + AttributionObservableRelatedDefinition, AttributionTargetsDefinition, AttributionUseDefinition, + BelongsToAttributedDefinition, IndicateSightedDefinition, + InfrastructureObservableRelatedDefinition, LocalizationOfTargetsDefinition, LocatedAtLocatedDefinition, LocationTargetsDefinition, @@ -39,6 +48,7 @@ export const rule_definitions = [ ReportRefIndicatorBasedOnDefinition, ReportRefLocationLocatedAtDefinition, ReportRefObservableBasedOnDefinition, + ReportRefObservableBelongsToDefinition, SightingIncidentDefinition, SightingIndicatorDefinition, SightingObservableDefinition, diff --git a/opencti-platform/opencti-graphql/src/types/stix-2-1-common.d.ts b/opencti-platform/opencti-graphql/src/types/stix-2-1-common.d.ts index e798809fd974..15c606d29630 100644 --- a/opencti-platform/opencti-graphql/src/types/stix-2-1-common.d.ts +++ b/opencti-platform/opencti-graphql/src/types/stix-2-1-common.d.ts @@ -71,8 +71,10 @@ interface StixOpenctiExtension { created_by_ref_id: string; created_by_ref_type: string; converter_csv?: string | undefined; - opencti_operation?: string; metrics: Array; + opencti_operation?: string; + opencti_field_patch?: EditInput[]; + opencti_upsert_operations?: EditInput[]; } interface StixOpenctiExtensionSDO extends StixOpenctiExtension { diff --git a/opencti-platform/opencti-graphql/src/utils/access.ts b/opencti-platform/opencti-graphql/src/utils/access.ts index 36d03aa239fa..bc7ebb49970f 100644 --- a/opencti-platform/opencti-graphql/src/utils/access.ts +++ b/opencti-platform/opencti-graphql/src/utils/access.ts @@ -25,6 +25,7 @@ import type { StixObject } from '../types/stix-2-1-common'; import { STIX_EXT_OCTI } from '../types/stix-2-1-extensions'; import type { BasicStoreCommon } from '../types/store'; import type { AuthContext, AuthUser, UserRole } from '../types/user'; +import { isFilterGroupNotEmpty } from './filtering/filtering-utils'; export const DEFAULT_INVALID_CONF_VALUE = 'ChangeMe'; @@ -601,6 +602,41 @@ export const isOnlyOrgaAdmin = (user: AuthUser) => { return !isUserHasCapability(user, SETTINGS_SET_ACCESSES) && isUserHasCapability(user, VIRTUAL_ORGANIZATION_ADMIN); }; +/** + * Construct a filter to restrict users visibility in case the user has not set_access capa and is organization administrator + */ +export const buildUserOrganizationRestrictedFiltersOptions = (user: AuthUser, inputFilters?: FilterGroup) => { + if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES)) { + // If user is not a set access administrator, user can only see attached organization users + const organizationIds = user.administrated_organizations.map((organization) => organization.id); + const filters = { + mode: 'and', + filters: [ + { + key: 'regardingOf', + operator: 'eq', + values: [ + { + key: 'relationship_type', + values: ['participate-to'], + }, + { + key: 'id', + values: organizationIds, + }, + ], + mode: 'or', + }, + ], + filterGroups: inputFilters && isFilterGroupNotEmpty(inputFilters) ? [inputFilters] : [], + }; + // dont check regardingOf filter id if user is admin of an orga, to avoid regardingOf filter error if the user has not access to his own organization + const noRegardingOfFilterIdsCheck = isOnlyOrgaAdmin(user) ? true : false; + return { filters, noRegardingOfFilterIdsCheck }; + } + return { filters: inputFilters, noRegardingOfFilterIdsCheck: false }; +}; + // returns all user member access ids : his id, his organizations ids (and parent organizations), his groups ids export const computeUserMemberAccessIds = (user: AuthUser) => { const memberAccessIds = [user.id]; diff --git a/opencti-platform/opencti-graphql/src/utils/authorizedMembers.ts b/opencti-platform/opencti-graphql/src/utils/authorizedMembers.ts index d46881e13ffb..2a71b672bc97 100644 --- a/opencti-platform/opencti-graphql/src/utils/authorizedMembers.ts +++ b/opencti-platform/opencti-graphql/src/utils/authorizedMembers.ts @@ -142,7 +142,7 @@ export const sanitizeAuthorizedMembers = (input: MemberAccessInput[]) => { }); }; -export const editAuthorizedMembers = async ( +export const buildRestrictedMembers = async ( context: AuthContext, user: AuthUser, args: { @@ -150,26 +150,21 @@ export const editAuthorizedMembers = async ( input: MemberAccessInput[] | undefined | null; requiredCapabilities: string[]; entityType: string; - busTopicKey?: keyof typeof BUS_TOPICS; // TODO improve busTopicKey types }, -) => { - const { entityId, input, requiredCapabilities, entityType, busTopicKey } = args; +): Promise => { + const { entityId, input, requiredCapabilities, entityType } = args; // Allow authorized members edition only on draft type but not for other entity types in draft const draftId = getDraftContext(context, user); if (draftId && draftId !== entityId) throw UnsupportedError('Cannot edit authorized members in draft'); - let restricted_members: { id: string; access_right: string; groups_restriction_ids: string[] | null | undefined }[] | null = null; - if (input) { // validate input (validate access right) remove duplicate const filteredInput = sanitizeAuthorizedMembers(input); - const settings = await getEntityFromCache(context, user, ENTITY_TYPE_SETTINGS); if (filteredInput.some(({ id }) => id === MEMBER_ACCESS_ALL) && settings.platform_organization && !isInternalObject(entityType)) { throw FunctionalError('You can\'t grant access to everyone in an organization sharing context'); } - const hasValidAdmin = await containsValidAdmin( context, filteredInput, @@ -178,7 +173,6 @@ export const editAuthorizedMembers = async ( if (!hasValidAdmin) { throw FunctionalError('It should have at least one valid member with admin access'); } - restricted_members = filteredInput.map(({ id, access_right, groups_restriction_ids }) => { const member = { id, access_right, groups_restriction_ids }; if (!groups_restriction_ids) { @@ -187,6 +181,28 @@ export const editAuthorizedMembers = async ( return member; }); } + return restricted_members; +}; + +export const editAuthorizedMembers = async ( + context: AuthContext, + user: AuthUser, + args: { + entityId: string; + input: MemberAccessInput[] | undefined | null; + requiredCapabilities: string[]; + entityType: string; + busTopicKey?: keyof typeof BUS_TOPICS; // TODO improve busTopicKey types + }, +) => { + const { entityId, input, requiredCapabilities, entityType, busTopicKey } = args; + + const restricted_members = await buildRestrictedMembers(context, user, { + entityId, + input, + requiredCapabilities, + entityType, + }); const patch = { restricted_members }; const { element } = await patchAttribute(context, user, entityId, entityType, patch); diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts index 670145441a95..eef898e204dc 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts @@ -13,8 +13,11 @@ import { INSTANCE_RELATION_TYPES_FILTER, IS_INFERRED_FILTER, isComplexConversionFilterKey, - LAST_PIR_SCORE_DATE_FILTER_PREFIX, - PIR_SCORE_FILTER_PREFIX, + LAST_PIR_SCORE_DATE_FILTER, + LAST_PIR_SCORE_DATE_SUBFILTER, + PIR_IDS_SUBFILTER, + PIR_SCORE_FILTER, + PIR_SCORE_SUBFILTER, RELATION_DYNAMIC_FROM_FILTER, RELATION_DYNAMIC_SUBFILTER, RELATION_DYNAMIC_TO_FILTER, @@ -55,8 +58,13 @@ import { getEntitiesListFromCache } from '../../database/cache'; import { ENTITY_TYPE_STATUS } from '../../schema/internalObject'; import { IDS_ATTRIBUTES } from '../../domain/attribute-utils'; -export const adaptFilterToRegardingOfFilterKey = async (context: AuthContext, user: AuthUser, filter: Filter) => { - const { key: filterKey } = filter; +export const adaptFilterToRegardingOfFilterKey = async ( + context: AuthContext, + user: AuthUser, + filter: Filter & { postFilteringTag?: string }, + noRegardingOfFilterIdsCheck = false, +) => { + const { key: filterKey, postFilteringTag } = filter; const regardingFilters = []; const idParameter = filter.values.find((i) => i.key === ID_SUBFILTER); const typeParameter = filter.values.find((i) => i.key === RELATION_TYPE_SUBFILTER); @@ -92,6 +100,13 @@ export const adaptFilterToRegardingOfFilterKey = async (context: AuthContext, us if (ids.length > 0) { // Keep ids the user has access to const filteredEntities = await elFindByIds(context, user, ids, { baseData: true }) as BasicStoreBase[]; + ids = noRegardingOfFilterIdsCheck + ? ids // if noRegardingOfFilterIdsCheck=true, we keep all the ids, even if the user has not access to them + : filteredEntities.map((n) => n.id); + if (ids.length === 0) { // If no id available, reject the query + throw ResourceNotFoundError('Specified ids not found or restricted'); + } + // If no type specified, we also need to check if the user have the correct capability for Pirs if (!typeParameter && !isUserHasCapability(user, PIRAPI)) { const isIncludingPir = (await uniqAsyncMap(filteredEntities, (value) => value.entity_type)) @@ -100,10 +115,6 @@ export const adaptFilterToRegardingOfFilterKey = async (context: AuthContext, us throw ForbiddenAccess('You are not allowed to use PIR filtering'); } } - ids = filteredEntities.map((n) => n.id); - if (ids.length === 0) { // If no id available, reject the query - throw ResourceNotFoundError('Specified ids not found or restricted'); - } } // Check dynamic const dynamicFilter = dynamicParameter?.values ?? []; @@ -130,13 +141,13 @@ export const adaptFilterToRegardingOfFilterKey = async (context: AuthContext, us ? buildRefRelationKey('*', '*') : types.map((t: string) => buildRefRelationKey(t, '*')); keys.forEach((relKey: string) => { - regardingFilters.push({ key: [relKey], operator: filter.operator, values: ['EXISTS'] }); + regardingFilters.push({ key: [relKey], operator: filter.operator, values: ['EXISTS'], postFilteringTag }); }); } else { const keys = isEmptyField(types) ? buildRefRelationKey('*', '*') : types.flatMap((t: string) => [buildRefRelationKey(t, ID_INTERNAL), buildRefRelationKey(t, ID_INFERRED)]); - regardingFilters.push({ key: keys, operator: filter.operator, mode, values: ids }); + regardingFilters.push({ key: keys, operator: filter.operator, mode, values: ids, postFilteringTag }); } return { newFilterGroup: { mode, filters: regardingFilters, filterGroups: [] } }; }; @@ -524,22 +535,21 @@ const adaptFilterToIsInferredFilterKey = (filter: Filter) => { }; const adaptFilterToPirFilterKeys = async (context: AuthContext, user: AuthUser, filterKey: string, filter: Filter) => { - // the key should be of format: pir_score.PIR_ID - const splittedKey = filterKey.split('.'); - if (splittedKey.length !== 2) { - throw FunctionalError('The filter key should be followed by a dot and the Pir ID', { filterKey }); + const pirIds: string[] = filter.values.find((v) => v.key === PIR_IDS_SUBFILTER)?.values ?? []; + if (pirIds.length === 0) { + throw FunctionalError('This filter should be related to at least 1 Pir', { filter }); } - const pirKey = splittedKey[0]; - const pirId = splittedKey[1]; // check the user has access to the PIR - await getPirWithAccessCheck(context, user, pirId); - // push the nested pir_score filter associated to the given PIR ID + await Promise.all(pirIds.map((pirId) => getPirWithAccessCheck(context, user, pirId))); + // push the nested pir filter associated to the given PIR IDs + const subKey = filterKey === PIR_SCORE_FILTER ? PIR_SCORE_SUBFILTER : LAST_PIR_SCORE_DATE_SUBFILTER; + const subFilter = filter.values.find((v) => v.key === subKey); const newFilter = { key: ['pir_information'], values: [], nested: [ - { ...filter, key: pirKey }, - { key: 'pir_id', values: [pirId], operator: FilterOperator.Eq }, + { ...subFilter, key: filterKey }, + { key: 'pir_id', values: pirIds, operator: FilterOperator.Eq }, ], }; return { newFilter, newFilterGroup: undefined }; @@ -697,13 +707,14 @@ export const completeSpecialFilterKeys = async ( context: AuthContext, user: AuthUser, inputFilters: FilterGroup, + opts?: { noRegardingOfFilterIdsCheck?: boolean }, ): Promise => { const { filters = [], filterGroups = [] } = inputFilters; const finalFilters = []; const finalFilterGroups: FilterGroup[] = []; for (let index = 0; index < filterGroups.length; index += 1) { const filterGroup = filterGroups[index]; - const newFilterGroup = await completeSpecialFilterKeys(context, user, filterGroup); + const newFilterGroup = await completeSpecialFilterKeys(context, user, filterGroup, opts); finalFilterGroups.push(newFilterGroup); } for (let index = 0; index < filters.length; index += 1) { @@ -716,7 +727,7 @@ export const completeSpecialFilterKeys = async ( } const filterKey = arrayKeys[0]; if (filterKey === INSTANCE_REGARDING_OF || filterKey === INSTANCE_DYNAMIC_REGARDING_OF) { - const { newFilterGroup } = await adaptFilterToRegardingOfFilterKey(context, user, filter); + const { newFilterGroup } = await adaptFilterToRegardingOfFilterKey(context, user, filter, opts?.noRegardingOfFilterIdsCheck); finalFilterGroups.push(newFilterGroup); } if (filterKey === IDS_FILTER) { @@ -811,7 +822,7 @@ export const completeSpecialFilterKeys = async ( finalFilterGroups.push(newFilterGroup); } } - if (filterKey.startsWith(PIR_SCORE_FILTER_PREFIX) || filterKey.startsWith(LAST_PIR_SCORE_DATE_FILTER_PREFIX)) { + if (filterKey === PIR_SCORE_FILTER || filterKey === LAST_PIR_SCORE_DATE_FILTER) { const { newFilter } = await adaptFilterToPirFilterKeys(context, user, filterKey, filter); finalFilters.push(newFilter); } diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts index 54d4437f81d5..16f31b769af8 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts @@ -101,9 +101,11 @@ export const ALIAS_FILTER = 'alias'; // handle both 'aliases' and 'x_opencti_ali export const IS_INFERRED_FILTER = 'is_inferred'; // if an entity or relationship is inferred // for PIR -export const PIR_SCORE_FILTER = 'pir_score'; // used in stix filtering -export const PIR_SCORE_FILTER_PREFIX = 'pir_score'; // used in dynamic filtering -export const LAST_PIR_SCORE_DATE_FILTER_PREFIX = 'last_pir_score_date'; +export const PIR_SCORE_FILTER = 'pir_score'; +export const LAST_PIR_SCORE_DATE_FILTER = 'last_pir_score_date'; +export const PIR_SCORE_SUBFILTER = 'score'; +export const LAST_PIR_SCORE_DATE_SUBFILTER = 'date'; +export const PIR_IDS_SUBFILTER = 'pir_ids'; // for users export const USER_SERVICE_ACCOUNT_FILTER = 'user_service_account'; @@ -136,12 +138,12 @@ const COMPLEX_CONVERSION_FILTER_KEYS = [ 'authorized_members.id', // nested filter on restricted members => kept for retro compatibility (TODO remove after renaming) 'restricted_members.id', // nested filter on restricted members BULK_SEARCH_KEYWORDS_FILTER, // set of keywords used in bulk search + PIR_SCORE_FILTER, // should be associated to Pir Ids + LAST_PIR_SCORE_DATE_FILTER, // should be associated to Pir Ids ]; export const isComplexConversionFilterKey = (filterKey: string) => { return COMPLEX_CONVERSION_FILTER_KEYS.includes(filterKey) - || filterKey.startsWith(PIR_SCORE_FILTER_PREFIX) - || filterKey.startsWith(LAST_PIR_SCORE_DATE_FILTER_PREFIX) || isMetricsName(filterKey); }; diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts index e59bf6c97d79..07f1a15ea092 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts @@ -46,6 +46,8 @@ import { WORKFLOW_FILTER, PATTERN_TYPE_FILTER, PIR_SCORE_FILTER, + PIR_SCORE_SUBFILTER, + PIR_IDS_SUBFILTER, } from '../filtering-constants'; import type { Filter } from '../../../generated/graphql'; import { STIX_RESOLUTION_MAP_PATHS } from '../filtering-resolution'; @@ -460,8 +462,8 @@ export const testCvssSeverity = (stix: any, filter: Filter) => { export const testPirScore = (stix: any, filter: Filter) => { // Retrieve data from the filter. - const pirIds = filter.values.find((v) => v.key === 'pir_ids')?.values ?? []; - const pirScoreFilter = filter.values.find((v) => v.key === 'score'); + const pirIds = filter.values.find((v) => v.key === PIR_IDS_SUBFILTER)?.values ?? []; + const pirScoreFilter = filter.values.find((v) => v.key === PIR_SCORE_SUBFILTER); // Determine which PIR of the stix entity is of interest. // If no PIR IDs set in the filter, then we want to check on all PIR of the stix entity. const pirInformation: PirInformation[] | undefined = pirIds.length === 0 diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts index b308473b9beb..0ef3bd3259c7 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts @@ -14,7 +14,6 @@ import { INSTANCE_DYNAMIC_REGARDING_OF, INSTANCE_REGARDING_OF, LABEL_FILTER, - LAST_PIR_SCORE_DATE_FILTER_PREFIX, ME_FILTER_VALUE, MEMBERS_GROUP_FILTER, MEMBERS_ORGANIZATION_FILTER, @@ -23,7 +22,6 @@ import { OPINIONS_METRICS_MEAN_FILTER, OPINIONS_METRICS_MIN_FILTER, OPINIONS_METRICS_TOTAL_FILTER, - PIR_SCORE_FILTER_PREFIX, RELATION_DYNAMIC_FROM_FILTER, RELATION_DYNAMIC_TO_FILTER, SIGHTED_BY_FILTER, @@ -463,8 +461,6 @@ const checkFilterKeys = (filterGroup: FilterGroup) => { .map((k) => k.split('.')[0]) // keep only the first part of the key to handle composed keys .filter((k) => !(getAvailableKeys().has(k) || k.startsWith(RULE_PREFIX) - || k.startsWith(PIR_SCORE_FILTER_PREFIX) - || k.startsWith(LAST_PIR_SCORE_DATE_FILTER_PREFIX) || getMetricsAttributesNames().includes(k) )); diff --git a/opencti-platform/opencti-graphql/src/utils/observable.ts b/opencti-platform/opencti-graphql/src/utils/observable.ts index e61481b99943..3b882369d076 100644 --- a/opencti-platform/opencti-graphql/src/utils/observable.ts +++ b/opencti-platform/opencti-graphql/src/utils/observable.ts @@ -340,3 +340,101 @@ export const groupByObservableType = (values: string[]): Record { + if (!value || typeof value !== 'string') { + return value; + } + + let refanged = value; + + // Protocol defanging: hxxp, hXXp, etc. → http + refanged = refanged.replace(/\bhxxps?:\/\//gi, (match) => { + return match.toLowerCase().replace('hxxp', 'http'); + }); + + // Handle protocol with brackets: hxxps[://] or [://] + refanged = refanged.replace(/\[:\/{2}\]/g, '://'); + refanged = refanged.replace(/\[:\/\/\]/g, '://'); + + // Protocol variations with mixed case + refanged = refanged.replace(/\bh[xX]{2}ps?\b/g, (match) => { + return match.toLowerCase().replace(/h[xX]{2}p/i, 'http'); + }); + + // Dot defanging: [.] or (.) or {.} → . + refanged = refanged.replace(/\[\.\]/g, '.'); + refanged = refanged.replace(/\(\.\)/g, '.'); + refanged = refanged.replace(/\{\.\}/g, '.'); + + // @ defanging: [@] or (@) or {@} → @ + refanged = refanged.replace(/\[@\]/g, '@'); + refanged = refanged.replace(/\(@\)/g, '@'); + refanged = refanged.replace(/\{@\}/g, '@'); + + // Colon defanging: [:] or (:) → : (often used for ports) + refanged = refanged.replace(/\[:\]/g, ':'); + refanged = refanged.replace(/\(:\)/g, ':'); + + // Slash defanging: [/] or (/) → / + refanged = refanged.replace(/\[\/\]/g, '/'); + refanged = refanged.replace(/\(\/\)/g, '/'); + + // Handle "dot" written out: test dot com → test.com + refanged = refanged.replace(/\s+dot\s+/gi, '.'); + + // Handle "at" written out: test at test.com → test@test.com + refanged = refanged.replace(/\s+at\s+/gi, '@'); + + // Remove spaces around dots that might have been added for safety + // e.g., "192 . 168 . 1 . 1" → "192.168.1.1" + refanged = refanged.replace(/\s*\.\s*/g, '.'); + + return refanged.trim(); +}; + +/** + * Refangs an array of values. + */ +export const refangValues = (values: string[]): string[] => { + return values.map((value) => refangValue(value)); +}; + +/** + * Checks if a value appears to be defanged (contains common defanging patterns). + */ +export const isDefanged = (value: string): boolean => { + if (!value || typeof value !== 'string') { + return false; + } + + const defangPatterns = [ + /hxxps?:\/\//i, // hxxp:// or hxxps:// + /\[\.\]/, // [.] + /\(\.\)/, // (.) + /\{\.\}/, // {.} + /\[@\]/, // [@] + /\(@\)/, // (@) + /\{@\}/, // {@} + /\[:\/{2}\]/, // [://] + /\[:\/\/\]/, // [://] + /\s+dot\s+/i, // " dot " + /\s+at\s+/i, // " at " + ]; + + return defangPatterns.some((pattern) => pattern.test(value)); +}; diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts index 42e0bc6e7f9d..ef4de3a50add 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts @@ -80,8 +80,8 @@ export const safeRender = async (template: string, data: Data, options?: SafeRen } }); - worker.on('error', (error) => { - once(() => reject(new Error(`Worker error: ${error.message}`))); + worker.on('error', (error: Error) => { + once(() => reject(new Error(`Worker error: ${error.message}`, { cause: error }))); }); worker.on('exit', (code) => { diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.ts index 14c5d1d761f8..43e7be6e872a 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.ts @@ -1,6 +1,6 @@ import { parser as jsParser } from '@lezer/javascript'; import type { Data, Options } from 'ejs'; -import { render } from 'ejs'; +import ejs from 'ejs'; import NotificationTool from './NotificationTool'; export abstract class VerifierError extends Error { @@ -69,6 +69,7 @@ const authorizeGlobals = new Map([ ['encodeURIComponent', true], ['decodeURI', true], ['decodeURIComponent', true], + ['escape', true], ]); const forbiddenGlobals = [ @@ -82,7 +83,10 @@ const forbiddenGlobals = [ const noop = () => {}; -const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxExecutionDuration = 0, yieldMethod }: SafeOptions) => { +const createSafeContext = ( + async: boolean, + { maxExecutedStatementCount = 0, maxExecutionDuration = 0, yieldMethod, escape }: SafeOptions & { escape?: (str: string) => string }, +) => { let executedStatementCount = 0; const checkMaxExecutedStatementCount = maxExecutedStatementCount > 0 ? () => { executedStatementCount += 1; @@ -98,7 +102,7 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE } } : noop; - return { + const context: Record = { [safeName('statement')]: async ? async () => { checkMaxExecutedStatementCount(); @@ -111,10 +115,11 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE }, [safeName('property')]: (propertyName: unknown) => { - if (typeof propertyName === 'string' && (propertyName.startsWith(safeReservedPrefix) || forbiddenProperties.has(propertyName))) { - throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName })}`); + const name = String(propertyName); + if (name.startsWith(safeReservedPrefix) || forbiddenProperties.has(name)) { + throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName: name })}`); } - return propertyName; + return name; }, [safeName('Object')]: Object.freeze({ @@ -127,17 +132,25 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE .filter((src) => src && typeof src === 'object') .forEach((src) => { Object.entries(src).forEach(([key, value]) => { - if (key.startsWith(safeReservedPrefix) || forbiddenProperties.has(key)) { - throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName: key })}`); + const name = String(key); // key should already be a string, but enforce it anyway + if (name.startsWith(safeReservedPrefix) || forbiddenProperties.has(name)) { + throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName: name })}`); } - target[key] = value; + target[name] = value; }); }); return target; }, }), }; + + // If a custom escape function is provided, make it available in template context + if (escape) { + context.escape = escape; + } + + return context; }; const extractEJSCode = (template: string, openTag: string, closeTag: string) => { @@ -387,5 +400,5 @@ export const safeRender = (template: string, data: Data, options: SafeRenderOpti const code = extractEJSCode(template, `${openDelimiter}${delimiter}`, `${delimiter}${closeDelimiter}`); const safeTemplate = transformTemplate(template, code, Object.keys(data ?? {})); const safeContext = createSafeContext(async, options); - return render(safeTemplate, { ...(data ?? {}), ...safeContext }, options); + return ejs.render(safeTemplate, { ...(data ?? {}), ...safeContext }, options); }; diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts index df898305fda3..40e4f1ec5714 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts @@ -16,6 +16,16 @@ export interface WorkerReply { error?: string; } +export const customEscapeFunction = (value: any): string => { + if (Array.isArray(value) && value.length === 0) { + return ''; + } + const result = JSON.stringify(value); + return result && result.startsWith('"') && result.endsWith('"') + ? result.slice(1, -1) + : result; +}; + // Main worker execution const executeWorker = async () => { const { template, data, options, useJsonEscape } = workerData as WorkerRequest; @@ -24,10 +34,7 @@ const executeWorker = async () => { const safeEjsOptions = { ...options }; if (useJsonEscape) { // Recreate the escape function for JSON stringification - safeEjsOptions.escape = (value: any) => { - const result = JSON.stringify(value); - return result.startsWith('"') && result.endsWith('"') ? result.slice(1, -1) : result; - }; + safeEjsOptions.escape = customEscapeFunction; } // Use the core logic from safeEjs (await in case it returns a Promise) diff --git a/opencti-platform/opencti-graphql/src/utils/totp.ts b/opencti-platform/opencti-graphql/src/utils/totp.ts new file mode 100644 index 000000000000..b71bd83d4764 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/utils/totp.ts @@ -0,0 +1,19 @@ +import { createGuardrails, OTP } from 'otplib'; + +const guardrails = createGuardrails({ + MIN_SECRET_BYTES: 10, // needed to support MFA code generated with otplib v12 +}); + +class GuardedOTP extends OTP { + // since OTP constructor can't support guardrails option for now, override the verify method accordingly + verify(options: Parameters[0]) { + return super.verify({ + ...options, + guardrails, + }); + } +} + +const totp = new GuardedOTP(); // defaults to TOTP strategy + +export { totp }; diff --git a/opencti-platform/opencti-graphql/src/utils/upsert-utils.js b/opencti-platform/opencti-graphql/src/utils/upsert-utils.js index 91de87d66315..a6f76e64b522 100644 --- a/opencti-platform/opencti-graphql/src/utils/upsert-utils.js +++ b/opencti-platform/opencti-graphql/src/utils/upsert-utils.js @@ -1,6 +1,6 @@ import * as R from 'ramda'; import moment from 'moment/moment'; -import { INTERNAL_USERS, isUserHasCapability, KNOWLEDGE_ORGANIZATION_RESTRICT } from './access'; +import { INTERNAL_USERS, isBypassUser, isUserHasCapability, KNOWLEDGE_ORGANIZATION_RESTRICT } from './access'; import { logApp } from '../config/conf'; import { storeFileConverter, uploadToStorage } from '../database/file-storage'; import { computeDateFromEventId, utcDate } from './format'; @@ -15,6 +15,7 @@ import { isStixCoreRelationship } from '../schema/stixCoreRelationship'; import { ENTITY_TYPE_CONTAINER_OBSERVED_DATA } from '../schema/stixDomainObject'; import { RELATION_CREATED_BY, RELATION_GRANTED_TO } from '../schema/stixRefRelationship'; import { isStixSightingRelationship } from '../schema/stixSightingRelationship'; +import { FunctionalError } from '../config/errors'; const ALIGN_OLDEST = 'oldest'; const ALIGN_NEWEST = 'newest'; @@ -191,6 +192,122 @@ const generateFileInputsForUpsert = async (context, user, resolvedElement, updat return []; }; +const mergeUpsertOperations = (upsertKey, elementCurrentValue, upsertOperations) => { + let currentValueArray = elementCurrentValue ?? []; + let mergedUpsertOperationValue = [...currentValueArray]; + let mergedUpsertOperationOperation; + for (let i = 0; i < upsertOperations.length; i++) { + const { operation: currentUpsertOperation, value: currentUpsertValue } = upsertOperations[i]; + if (currentUpsertOperation === 'remove') { + // filter values to remove from current values in DB + mergedUpsertOperationValue = mergedUpsertOperationValue.filter((e) => + !currentUpsertValue?.includes(e) && (!e?.id || !currentUpsertValue?.some((u) => u?.id === e?.id)), + ); + mergedUpsertOperationOperation = 'replace'; + } else if (currentUpsertOperation === 'replace') { + // replace current values in DB with upsert values + mergedUpsertOperationValue = [...(currentUpsertValue ?? [])]; + mergedUpsertOperationOperation = 'replace'; + } else if (currentUpsertOperation === 'add') { + // add upsert operation values to final patch values first + mergedUpsertOperationValue.push(...(currentUpsertValue ?? [])); + mergedUpsertOperationOperation = (!mergedUpsertOperationOperation || mergedUpsertOperationOperation === 'add') ? 'add' : 'replace'; + } + } + return { key: upsertKey, operation: mergedUpsertOperationOperation, value: mergedUpsertOperationValue }; +}; + +export const mergeUpsertInput = (elementCurrentValue, upsertValue, updatePatchInput, upsertOperation) => { + const finalPatchInput = { ...updatePatchInput }; + // for now we only handle 'add' operations coming from updatePatchInput for multiple attributes + // we need to first apply the upsertOperation on element then the updatePatchInput + if (updatePatchInput.operation === 'add' && upsertOperation?.operation) { + let currentValueArray = elementCurrentValue ?? []; + currentValueArray = Array.isArray(currentValueArray) ? currentValueArray : [currentValueArray]; + let finalPatchValue = [...currentValueArray]; + if (upsertOperation.operation === 'remove') { + // filter values to remove from current values in DB + finalPatchValue = finalPatchValue.filter((e) => !upsertOperation.value?.includes(e) && (!e?.id || !upsertOperation.value?.some((u) => u?.id === e?.id))); + finalPatchInput.operation = 'replace'; + } else if (upsertOperation.operation === 'replace') { + // replace current values in DB with upsert values + finalPatchValue = [...(upsertOperation?.value ?? [])]; + finalPatchInput.operation = 'replace'; + } else if (upsertOperation.operation === 'add') { + // add upsert operation values to final patch values first + finalPatchValue = [...(upsertOperation.value ?? [])]; + finalPatchInput.operation = 'add'; + } + // add updatePatchInput values coming from upsert + if (updatePatchInput.value?.length > 0) { + finalPatchValue.push(...updatePatchInput.value); + } + // keep only unique values + let finalDedupedPatchValuesMap = new Map(); + for (let i = 0; i < finalPatchValue.length; i++) { + const currentPatchValue = finalPatchValue[i]; + if (!finalDedupedPatchValuesMap.has(currentPatchValue) && !finalDedupedPatchValuesMap.has(currentPatchValue?.id)) { + if (currentPatchValue?.id) { + finalDedupedPatchValuesMap.set(currentPatchValue.id, currentPatchValue); + } else { + finalDedupedPatchValuesMap.set(currentPatchValue, currentPatchValue); + } + } + } + // we replace current values + finalPatchInput.value = Array.from(finalDedupedPatchValuesMap.values()); + } + return finalPatchInput; +}; + +/** + * should return a merged inputs list with only one element per key + * + * @param resolvedElement (element from DB) + * @param updatePatch (element from bundle) + * @param updatePatchInputs : array inputs generated from updatePatch (from element in bundle) + * @param upsertOperations : array inputs from upsertOperations in bundle + */ +export const mergeUpsertInputs = (resolvedElement, updatePatch, updatePatchInputs, upsertOperations) => { + // we want only to call this method for remove or replace operations that should happen on arrays + if (!upsertOperations || upsertOperations.length === 0) { + return updatePatchInputs; + } + + const updatePatchInputsMap = new Map(updatePatchInputs.map((input) => [input.key, input])); + const upsertOperationsByKeyMap = new Map(); + for (let i = 0; i < upsertOperations.length; i += 1) { + const currentUpsertOperation = upsertOperations[i]; + if (upsertOperationsByKeyMap.has(currentUpsertOperation.key)) { + upsertOperationsByKeyMap.get(currentUpsertOperation.key).push(currentUpsertOperation); + } else { + upsertOperationsByKeyMap.set(currentUpsertOperation.key, [currentUpsertOperation]); + } + } + const upsertOperationsKeys = Array.from(upsertOperationsByKeyMap.keys()); + for (let i = 0; i < upsertOperationsKeys.length; i += 1) { + const upsertOperationKey = upsertOperationsKeys[i]; + const elementCurrentValue = resolvedElement[upsertOperationKey]; + const upsertOperationValues = upsertOperationsByKeyMap.get(upsertOperationKey); + let finalUpsertOperation; + if (upsertOperationValues.length > 1) { + finalUpsertOperation = mergeUpsertOperations(upsertOperationKey, elementCurrentValue, upsertOperationValues); + } else { + finalUpsertOperation = upsertOperationValues[0]; + } + if (updatePatchInputsMap.has(upsertOperationKey)) { + const updatePatchInput = updatePatchInputsMap.get(upsertOperationKey); + const elementCurrentValue = resolvedElement[upsertOperationKey]; + const upsertValue = updatePatch[upsertOperationKey]; + const mergedInput = mergeUpsertInput(elementCurrentValue, upsertValue, updatePatchInput, finalUpsertOperation); + updatePatchInputsMap.set(upsertOperationKey, mergedInput); // replace updatePatchInput + } else { + updatePatchInputsMap.set(upsertOperationKey, finalUpsertOperation); // just add the upsert operation + } + } + return Array.from(updatePatchInputsMap.values()); +}; + export const generateAttributesInputsForUpsert = (context, _user, resolvedElement, type, updatePatch, confidenceForUpsert) => { const { isConfidenceMatch } = confidenceForUpsert; // -- Upsert attributes @@ -302,5 +419,11 @@ export const generateInputsForUpsert = async (context, user, resolvedElement, ty const refsInputs = generateRefsInputsForUpsert(context, user, resolvedElement, type, updatePatch, confidenceForUpsert, validEnterpriseEdition); inputs.push(...refsInputs); - return inputs; + // -- merge inputs with upsertOperations + if (updatePatch.upsertOperations?.length > 0 && !isBypassUser(user)) { + throw FunctionalError('User has insufficient rights to use upsertOperations', { user_id: user.id, element_id: resolvedElement.id }); + } + const finalInputs = mergeUpsertInputs(resolvedElement, updatePatch, inputs, updatePatch.upsertOperations); + + return finalInputs; }; diff --git a/opencti-platform/opencti-graphql/tests/01-unit/database/middleware-test.js b/opencti-platform/opencti-graphql/tests/01-unit/database/middleware-test.js index 1c6474a5e6b5..898df8c83692 100644 --- a/opencti-platform/opencti-graphql/tests/01-unit/database/middleware-test.js +++ b/opencti-platform/opencti-graphql/tests/01-unit/database/middleware-test.js @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; import { hashMergeValidation } from '../../../src/database/middleware'; -import {generateAttributesInputsForUpsert} from "../../../src/utils/upsert-utils"; -import {ADMIN_USER, testContext} from "../../utils/testQuery"; -import {ENTITY_DOMAIN_NAME} from "../../../src/schema/stixCyberObservable"; +import { generateAttributesInputsForUpsert, mergeUpsertInput, mergeUpsertInputs } from '../../../src/utils/upsert-utils'; +import { ADMIN_USER, testContext } from '../../utils/testQuery'; +import { ENTITY_DOMAIN_NAME } from '../../../src/schema/stixCyberObservable'; describe('middleware hashMergeValidation test', () => { it('should hashes allowed to merge', () => { @@ -44,35 +44,337 @@ describe('middleware upsertElement test', () => { it('should generateAttributesInputsForUpsert with indicator description update no old description', () => { const resolvedElement = { ...indicator1 }; const type = 'Indicator'; - const updatePatch = { - standard_id: 'indicator1-uuid-standard', - description: 'indicator1 new description', - }; - let confidenceForUpsert = { isConfidenceMatch: true } + + let confidenceForUpsert = { isConfidenceMatch: true }; let inputs = generateAttributesInputsForUpsert(testContext, ADMIN_USER, resolvedElement, type, updatePatch, confidenceForUpsert); expect(inputs.length).toEqual(1); - expect(inputs[0]).toEqual({key: 'description', value: ['indicator1 new description']}) + expect(inputs[0]).toEqual({ key: 'description', value: ['indicator1 new description'] }); - confidenceForUpsert = { isConfidenceMatch: false } + confidenceForUpsert = { isConfidenceMatch: false }; inputs = generateAttributesInputsForUpsert(testContext, ADMIN_USER, resolvedElement, type, updatePatch, confidenceForUpsert); expect(inputs.length).toEqual(1); // we still update description since no existing - expect(inputs[0]).toEqual({key: 'description', value: ['indicator1 new description']}) + expect(inputs[0]).toEqual({ key: 'description', value: ['indicator1 new description'] }); }); it('should generateAttributesInputsForUpsert with indicator description update', () => { const resolvedElement = { ...indicator1, description: 'indicator1 old description' }; // existing description - let confidenceForUpsert = { isConfidenceMatch: true } + let confidenceForUpsert = { isConfidenceMatch: true }; let inputs = generateAttributesInputsForUpsert(testContext, ADMIN_USER, resolvedElement, type, updatePatch, confidenceForUpsert); expect(inputs.length).toEqual(1); - expect(inputs[0]).toEqual({key: 'description', value: ['indicator1 new description']}) + expect(inputs[0]).toEqual({ key: 'description', value: ['indicator1 new description'] }); - confidenceForUpsert = { isConfidenceMatch: false } + confidenceForUpsert = { isConfidenceMatch: false }; inputs = generateAttributesInputsForUpsert(testContext, ADMIN_USER, resolvedElement, type, updatePatch, confidenceForUpsert); expect(inputs.length).toEqual(0); // no changes since confidenceMatch is false, we don't replace existing description }); + + it('should generateAttributesInputsForUpsert with indicator description update & indicator types unchanged', () => { + const resolvedElement = { ...indicator1, description: 'indicator1 old description', indicator_types: ['type1', 'type2'] }; + const updatePatchWithTypes = { ...updatePatch, indicator_types: ['type1', 'type2'] }; + let confidenceForUpsert = { isConfidenceMatch: true }; + let inputs = generateAttributesInputsForUpsert(testContext, ADMIN_USER, resolvedElement, type, updatePatchWithTypes, confidenceForUpsert); + + expect(inputs.length).toEqual(2); + expect(inputs[0]).toEqual({ key: 'description', value: ['indicator1 new description'] }); + expect(inputs[1]).toEqual({ key: 'indicator_types', value: ['type1', 'type2'], operation: 'add' }); + + confidenceForUpsert = { isConfidenceMatch: false }; + inputs = generateAttributesInputsForUpsert(testContext, ADMIN_USER, resolvedElement, type, updatePatchWithTypes, confidenceForUpsert); + + expect(inputs.length).toEqual(0); // no changes since confidenceMatch is false, we don't replace existing description + }); + }); + describe('middleware generateAttributesInputsForUpsert with opencti_upsert_operations test', () => { + const indicator1 = { + id: 'indicator1-uuid-internal', + internal_id: 'indicator1-uuid-internal', + standard_id: 'indicator1-uuid-standard', + pattern: '[domain-name:value = \'filigran.dev\']', + pattern_type: 'stix', + x_opencti_main_observable_type: ENTITY_DOMAIN_NAME, + indicator_types: ['ip', 'active-directory'], + }; + const indicatorUpsertOperations = [ + { + operation: 'remove', + key: 'indicator_types', + value: ['active-directory'], + }, + ]; + const updatePatchIndicator1 = { + standard_id: 'indicator1-uuid-standard', + // indicator_types: ['malicious-activity'], + indicatorUpsertOperations, + }; + it('should mergeUpsertInputs with indicator : different keys', () => { + const updatePatch = { ...updatePatchIndicator1, description: 'indicator new description' }; + const updatePatchInput = [{ + operation: 'replace', + key: 'description', + value: ['indicator new description'], + }]; + const inputs = mergeUpsertInputs(indicator1, updatePatch, updatePatchInput, indicatorUpsertOperations); + + expect(inputs.length).toEqual(2); + expect(inputs.find((n) => n.key === 'description')).toEqual({ operation: 'replace', key: 'description', value: ['indicator new description'] }); + expect(inputs.find((n) => n.key === 'indicator_types')).toEqual({ operation: 'remove', key: 'indicator_types', value: ['active-directory'] }); + }); + it('should mergeUpsertInput with indicator : upsert adds type and operation removes another type', () => { + const updatePatchInput = { + operation: 'add', + key: 'indicator_types', + value: ['indicator-type-to-add'], + }; + const upsertOperation = { + operation: 'remove', + key: 'indicator_types', + value: ['indicator-type-to-remove'], + }; + const elementCurrentValue = ['indicator-type-to-remove', 'indicator-type-current']; + const upsertCurrentValue = ['indicator-type-current', 'indicator-type-to-add']; + const input = mergeUpsertInput(elementCurrentValue, upsertCurrentValue, updatePatchInput, upsertOperation); + + // inputs should be operation: 'replace', value: ['indicator-type-current', 'indicator-type-to-add'] + expect(input.key).toEqual('indicator_types'); + expect(input.operation).toEqual('replace'); + expect(input.value.length).toEqual(2); + expect(input.value.includes('indicator-type-current')).toBe(true); + expect(input.value.includes('indicator-type-to-add')).toBe(true); + }); + + it('should mergeUpsertInput with indicator : upsert adds back the same type that was removed, not in DB', () => { + const updatePatchInput = { + operation: 'add', + key: 'indicator_types', + value: ['indicator-type-1'], + }; + // remove operation done before, bundle contains again the type, we should keep it + const upsertOperation = { + operation: 'remove', + key: 'indicator_types', + value: ['indicator-type-1'], + }; + const elementCurrentValue = ['indicator-type-current']; + const upsertCurrentValue = ['indicator-type-1', 'indicator-type-current']; + const input = mergeUpsertInput(elementCurrentValue, upsertCurrentValue, updatePatchInput, upsertOperation); + + // input should be operation: 'replace', value: ['indicator-type-1', 'indicator-type-current'] + expect(input.key).toEqual('indicator_types'); + expect(input.operation).toEqual('replace'); + expect(input.value.length).toEqual(2); + expect(input.value.includes('indicator-type-current')).toBe(true); + expect(input.value.includes('indicator-type-1')).toBe(true); + }); + + // skip this one for now, not implemented yet + it.skip('should mergeUpsertInput with indicator : upsert adds back the same type that was removed, already in DB', () => { + const updatePatchInput = { + operation: 'add', + key: 'indicator_types', + value: ['indicator-type-1'], + }; + // remove operation done before, bundle contains again the type, we should keep it + const upsertOperation = { + operation: 'remove', + key: 'indicator_types', + value: ['indicator-type-1'], + }; + const elementCurrentValue = ['indicator-type-1', 'indicator-type-current']; + const upsertCurrentValue = ['indicator-type-1', 'indicator-type-current']; + const inputs = mergeUpsertInput(elementCurrentValue, upsertCurrentValue, updatePatchInput, upsertOperation); + + // TODO inputs should be empty + console.log('inputs', inputs); + }); + const labelToRemove = { + _id: '0fc83074-eb2d-47f6-b315-781e511f0322', + _index: 'opencti_stix_meta_objects-000001', + base_type: 'ENTITY', + color: '#320dff', + confidence: 100, + created: '2025-07-02T15:23:00.703Z', + created_at: '2025-07-02T15:23:00.703Z', + creator_id: [ + '88ec0c6a-13ce-5e39-b486-354fe4a7084f', + ], + entity_type: 'Label', + id: '0fc83074-eb2d-47f6-b315-781e511f0322', + internal_id: '0fc83074-eb2d-47f6-b315-781e511f0322', + modified: '2025-07-02T15:23:00.703Z', + parent_types: [ + 'Basic-Object', + 'Stix-Object', + 'Stix-Meta-Object', + ], + standard_id: 'label--00785e04-8bf4-52ee-bba2-88ccecc17b8d', + updated_at: '2025-07-02T15:23:00.703Z', + value: 'label-to-remove', + }; + it('should mergeUpsertInputs with indicator with only upsertOperation with remove label', () => { + const updatePatch = { ...updatePatchIndicator1, description: 'indicator new description' }; + const upsertOperations = [{ + operation: 'remove', + key: 'objectLabel', + value: [labelToRemove], + }]; + const updatePatchInput = [{ + operation: 'replace', + key: 'description', + value: ['indicator new description'], + }]; + const inputs = mergeUpsertInputs(indicator1, updatePatch, updatePatchInput, upsertOperations); + + expect(inputs.length).toEqual(2); + expect(inputs.find((n) => n.key === 'description')).toEqual({ operation: 'replace', key: 'description', value: ['indicator new description'] }); + expect(inputs.find((n) => n.key === 'objectLabel')).toEqual({ operation: 'remove', key: 'objectLabel', value: [labelToRemove] }); + }); + it('should mergeUpsertInput with indicator : upsert adds label and operation removes another label', () => { + const labelToAddId = 'eda3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelToAdd = { ...labelToRemove, value: 'label-to-add', id: labelToAddId, internal_id: labelToAddId, standard_id: labelToAddId }; + const labelCurrentId = 'ada3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelCurrentValue = { ...labelToRemove, value: 'label-current', id: labelCurrentId, internal_id: labelCurrentId, standard_id: labelCurrentId }; + const updatePatchInput = { + operation: 'add', + key: 'objectLabel', + value: [labelToAdd], + }; + const upsertOperation = { + operation: 'remove', + key: 'objectLabel', + value: [labelToRemove], + }; + const elementCurrentValue = [labelToRemove, labelCurrentValue]; + const upsertCurrentValue = [labelCurrentValue, labelToAdd]; + const input = mergeUpsertInput(elementCurrentValue, upsertCurrentValue, updatePatchInput, upsertOperation); + + // inputs should be operation: 'replace', value: [labelCurrentValue, labelToAdd] + expect(input.key).toEqual('objectLabel'); + expect(input.operation).toEqual('replace'); + expect(input.value.length).toEqual(2); + expect(input.value.some((v) => v.value === labelCurrentValue.value)).toBe(true); + expect(input.value.some((v) => v.value === labelToAdd.value)).toBe(true); + }); + it('should mergeUpsertInputs with indicator : upsert adds label and operation removes another label', () => { + const labelToAddId = 'eda3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelToAdd = { ...labelToRemove, value: 'label-to-add', id: labelToAddId, internal_id: labelToAddId, standard_id: labelToAddId }; + const labelCurrentId = 'ada3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelCurrentValue = { ...labelToRemove, value: 'label-current', id: labelCurrentId, internal_id: labelCurrentId, standard_id: labelCurrentId }; + const updatePatchInput = { + operation: 'add', + key: 'objectLabel', + value: [labelToAdd], + }; + const upsertOperation = { + operation: 'remove', + key: 'objectLabel', + value: [labelToRemove], + }; + const currentIndicator = { ...indicator1, objectLabel: [labelCurrentValue, labelToRemove] }; + const updatePatch = { ...indicator1, objectLabel: [labelCurrentValue, labelToAdd] }; + const inputs = mergeUpsertInputs(currentIndicator, updatePatch, [updatePatchInput], [upsertOperation]); + + // inputs should be operation: 'replace', value: [labelCurrentValue, labelToAdd] + expect(inputs.length).toEqual(1); + expect(inputs.find((n) => n.key === 'objectLabel')).toEqual({ operation: 'replace', key: 'objectLabel', value: [labelCurrentValue, labelToAdd] }); + }); + it('should mergeUpsertInput with indicator : upsert adds label and operation replaces labels', () => { + const labelToAddId = 'eda3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelToAdd = { ...labelToRemove, value: 'label-to-add', id: labelToAddId, internal_id: labelToAddId, standard_id: labelToAddId }; + const labelCurrentId = 'ada3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelCurrentValue = { ...labelToRemove, value: 'label-current', id: labelCurrentId, internal_id: labelCurrentId, standard_id: labelCurrentId }; + const labelToReplaceId = '2b13fe77-429f-4c69-8102-42c57741c79a'; + const labelToReplace = { ...labelToRemove, value: 'label-replace', id: labelToReplaceId, internal_id: labelToReplaceId, standard_id: labelToReplaceId }; + const updatePatchInput = { + operation: 'add', + key: 'objectLabel', + value: [labelToAdd], + }; + const upsertOperation = { + operation: 'replace', + key: 'objectLabel', + value: [labelToReplace], + }; + const elementCurrentValue = [labelCurrentValue]; + const upsertCurrentValue = [labelCurrentValue, labelToAdd]; + const input = mergeUpsertInput(elementCurrentValue, upsertCurrentValue, updatePatchInput, upsertOperation); + + // inputs should be operation: 'replace', value: [labelCurrentValue, labelToAdd] + expect(input.key).toEqual('objectLabel'); + expect(input.operation).toEqual('replace'); + expect(input.value.length).toEqual(2); + expect(input.value.some((v) => v.value === labelToAdd.value)).toBe(true); + expect(input.value.some((v) => v.value === labelToReplace.value)).toBe(true); + }); + it('should mergeUpsertInput with indicator : upsert adds label and operation add labels', () => { + const labelToAddId = 'eda3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelToAdd = { ...labelToRemove, value: 'label-to-add', id: labelToAddId, internal_id: labelToAddId, standard_id: labelToAddId }; + const labelCurrentId = 'ada3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelCurrentValue = { ...labelToRemove, value: 'label-current', id: labelCurrentId, internal_id: labelCurrentId, standard_id: labelCurrentId }; + const labelToUpsertAddId = 'f6a6c3a5-67f6-4a90-b2a9-a1d980ec7ff2'; + const labelToUpsertAdd = { ...labelToRemove, value: 'label-upsert-add', id: labelToUpsertAddId, internal_id: labelToUpsertAddId, standard_id: labelToUpsertAddId }; + const updatePatchInput = { + operation: 'add', + key: 'objectLabel', + value: [labelToAdd], + }; + const upsertOperation = { + operation: 'add', + key: 'objectLabel', + value: [labelToUpsertAdd], + }; + const elementCurrentValue = [labelCurrentValue]; + const upsertCurrentValue = [labelCurrentValue, labelToAdd]; + const input = mergeUpsertInput(elementCurrentValue, upsertCurrentValue, updatePatchInput, upsertOperation); + + // inputs should be operation: 'replace', value: [labelCurrentValue, labelToAdd] + expect(input.key).toEqual('objectLabel'); + expect(input.operation).toEqual('add'); + expect(input.value.length).toEqual(2); + expect(input.value.some((v) => v.value === labelToAdd.value)).toBe(true); + expect(input.value.some((v) => v.value === labelToUpsertAdd.value)).toBe(true); + }); + it('should mergeUpsertInputss with indicator : upsert adds label and operations add labels, replace labels and remove label', () => { + const labelToAddId = 'eda3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelToAdd = { ...labelToRemove, value: 'label-to-add', id: labelToAddId, internal_id: labelToAddId, standard_id: labelToAddId }; + const labelToAdd2Id = 'f5cd3534-5e4b-4176-865a-d1ee73566d9c'; + const labelToAdd2 = { ...labelToRemove, value: 'label-to-add2', id: labelToAdd2Id, internal_id: labelToAdd2Id, standard_id: labelToAdd2Id }; + const labelToReplaceId = 'eaab8db2-253d-4c67-9549-56be027ee81c'; + const labelToReplace = { ...labelToRemove, value: 'label-to-replace', id: labelToReplaceId, internal_id: labelToReplaceId, standard_id: labelToReplaceId }; + const labelCurrentId = 'ada3b7d5-b722-4c34-9dd5-d70cea8f5cfc'; + const labelCurrentValue = { ...labelToRemove, value: 'label-current', id: labelCurrentId, internal_id: labelCurrentId, standard_id: labelCurrentId }; + const updatePatchInput = { + operation: 'add', + key: 'objectLabel', + value: [labelToAdd], + }; + const upsertOperations = [ + { + operation: 'remove', + key: 'objectLabel', + value: [labelToAdd], + }, + { + operation: 'replace', + key: 'objectLabel', + value: [labelToReplace], + }, + { + operation: 'add', + key: 'objectLabel', + value: [labelToAdd2], + }, + ]; + const currentIndicator = { ...indicator1, objectLabel: [labelCurrentValue, labelToRemove] }; + const updatePatch = { ...indicator1, objectLabel: [labelCurrentValue, labelToAdd] }; + const inputs = mergeUpsertInputs(currentIndicator, updatePatch, [updatePatchInput], upsertOperations); + + // inputs should be operation: 'replace', value: [labelCurrentValue, labelToAdd] + expect(inputs.length).toEqual(1); + expect(inputs.find((n) => n.key === 'objectLabel')).toEqual({ operation: 'replace', key: 'objectLabel', value: [labelToReplace, labelToAdd2, labelToAdd] }); + }); }); }); diff --git a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json new file mode 100644 index 000000000000..8bfef84c5bf4 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json @@ -0,0 +1,17 @@ +<% + var inst = (data && data[0] && data[0].instance) ? data[0].instance : {}; +%>{ + "source": "OpenCTI", + "event": "New OpenCTI Report", + "id": "<%=notification.id%>3", + "timestamp":"<%= ((+new Date()) / 1000) | 0 %>", + "reports": [ + { + "title": "<%= (content && content[0] && content[0].title) ? content[0].title : '' %>", + "description": "<%= inst.description || '' %>", + "report_id":"<%=content[0].events[0].instance_id %>", + "published": "<%= inst.published || '' %>", + "author": "<%= inst.created_by_ref || inst.created_by_ref_id || 'Unknown' %>", + } + ] +} \ No newline at end of file diff --git a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts index 636815db8749..8eb8a31fcb10 100644 --- a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts +++ b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; -import { render } from 'ejs'; +import ejs from 'ejs'; import { safeRender } from '../../../src/utils/safeEjs'; import { safeRender as safeRenderClient } from '../../../src/utils/safeEjs.client'; +import { customEscapeFunction } from '../../../src/utils/safeEjs.worker'; const testFilename = fileURLToPath(import.meta.url); @@ -44,7 +45,7 @@ describe('check safeRender on valid cases', () => { it.each(validCases)('safeRender should succeed for "$name" case', ({ template }) => { const safeRendered = safeRender(template, data); - const unsafeRendered = render(template, data); + const unsafeRendered = ejs.render(template, data); expect(safeRendered).toEqual(unsafeRendered); }); }); @@ -126,11 +127,11 @@ describe('check safeRender with NotificationTool (markdown)', () => { it('should render markdown to HTML with useNotificationTool flag', async () => { const template = '<%- octi.markdownToHtml(description) %>'; const data = { - description: '# Title\n\nThis is **bold** and *italic* text.' + description: '# Title\n\nThis is **bold** and *italic* text.', }; - + const result = await safeRenderClient(template, data, { useNotificationTool: true }); - + expect(result).toContain('

        Title

        '); expect(result).toContain('bold'); expect(result).toContain('italic'); @@ -139,11 +140,11 @@ describe('check safeRender with NotificationTool (markdown)', () => { it('should handle markdown with lists', async () => { const template = '<%- octi.markdownToHtml(content) %>'; const data = { - content: '- Item 1\n- Item 2\n- Item 3' + content: '- Item 1\n- Item 2\n- Item 3', }; - + const result = await safeRenderClient(template, data, { useNotificationTool: true }); - + expect(result).toContain('
          '); expect(result).toContain('
        • Item 1
        • '); expect(result).toContain('
        • Item 2
        • '); @@ -153,11 +154,11 @@ describe('check safeRender with NotificationTool (markdown)', () => { it('should handle undefined markdown gracefully', async () => { const template = '<%- octi.markdownToHtml(description) || "No description" %>'; const data = { - description: undefined + description: undefined, }; - + const result = await safeRenderClient(template, data, { useNotificationTool: true }); - + expect(result).toBe('No description'); }); @@ -173,12 +174,12 @@ describe('check safeRender with NotificationTool (markdown)', () => { const data = { data: [ { title: 'Item 1', description: '**Important** information' }, - { title: 'Item 2', description: 'Another *description*' } - ] + { title: 'Item 2', description: 'Another *description*' }, + ], }; - + const result = await safeRenderClient(template, data, { useNotificationTool: true }); - + expect(result).toContain('

          Item 1

          '); expect(result).toContain('Important'); expect(result).toContain('

          Item 2

          '); @@ -188,12 +189,12 @@ describe('check safeRender with NotificationTool (markdown)', () => { it('should fail when useNotificationTool flag is not set', async () => { const template = '<%- octi.markdownToHtml(description) %>'; const data = { - description: '# Title' + description: '# Title', }; - + // Without the flag, octi should not be available await expect( - safeRenderClient(template, data) + safeRenderClient(template, data), ).rejects.toThrow(/octi/i); }); }); @@ -203,9 +204,9 @@ describe('check safeRenderClient error handling and worker termination detection // Template with infinite loop should timeout const template = '<% while(true) {} %>'; const data = {}; - + await expect( - safeRenderClient(template, data, { timeout: 100 }) + safeRenderClient(template, data, { timeout: 100 }), ).rejects.toThrow(/timeout after 100ms/i); }); @@ -213,9 +214,9 @@ describe('check safeRenderClient error handling and worker termination detection // Template that causes a worker error const template = '<%= nonExistentVariable.property %>'; const data = {}; - + await expect( - safeRenderClient(template, data, { timeout: 5000 }) + safeRenderClient(template, data, { timeout: 5000 }), ).rejects.toThrow(/nonExistentVariable/i); }); @@ -223,17 +224,17 @@ describe('check safeRenderClient error handling and worker termination detection // Template that tries to allocate too much memory const template = '<% const arr = new Array(1000000000).fill("x"); %><%= arr.length %>'; const data = {}; - + await expect( - safeRenderClient(template, data, { + safeRenderClient(template, data, { timeout: 5000, resourceLimits: { maxOldGenerationSizeMb: 10, maxYoungGenerationSizeMb: 5, codeRangeSizeMb: 5, stackSizeMb: 2, - } - }) + }, + }), ).rejects.toThrow(); }); @@ -241,9 +242,9 @@ describe('check safeRenderClient error handling and worker termination detection // Template with syntax error const template = '<% const x = ; %>'; const data = {}; - + await expect( - safeRenderClient(template, data) + safeRenderClient(template, data), ).rejects.toThrow(); }); @@ -251,9 +252,9 @@ describe('check safeRenderClient error handling and worker termination detection // Template that causes a runtime error (division by zero leads to Infinity, but accessing undefined property causes error) const template = '<%= undefined.nonExistentProperty %>'; const data = {}; - + await expect( - safeRenderClient(template, data) + safeRenderClient(template, data), ).rejects.toThrow(/undefined/i); }); @@ -261,21 +262,112 @@ describe('check safeRenderClient error handling and worker termination detection // Template trying to access undefined deeply nested property const template = '<%= data.deep.nested.property.that.does.not.exist %>'; const data = {}; - + await expect( - safeRenderClient(template, data) + safeRenderClient(template, data), ).rejects.toThrow(); }); it('should succeed with valid template and reasonable timeout', async () => { const template = '<% for(let i = 0; i < 1000; i++) {} %>Success'; const data = {}; - + const result = await safeRenderClient(template, data, { timeout: 5000 }); expect(result).toBe('Success'); }); }); +describe('check safeRender with escape function', () => { + it('should allow escape function in templates when provided via options', () => { + // Use <%- %> (raw output) to avoid double-escaping when escape() is called explicitly + const template = '<% function parseLink(text) { return escape(text); } %><%- parseLink("") %>'; + const data = {}; + const escapeFunc = (str: any) => String(str).replace(/[&<>"']/g, (char) => { + const escapeMap: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return escapeMap[char] || char; + }); + + const result = safeRender(template, data, { escape: escapeFunc }); + expect(result).toContain('<script>'); + expect(result).toContain('</script>'); + expect(result).not.toContain('](javascript:alert(1))') %> + `; + const data = {}; + const escapeFunc = (str: any) => String(str).replace(/[&<>"']/g, (char) => { + const escapeMap: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return escapeMap[char] || char; + }); + + const result = safeRender(template, data, { escape: escapeFunc }); + // The escape function should escape the special characters + expect(result).not.toContain('