Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/secops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
#
"""Google SecOps SDK for Python."""

__version__ = "0.1.2"
from importlib.metadata import version as _metadata_version

__version__ = _metadata_version("secops")

from secops.auth import SecOpsAuth
from secops.client import SecOpsClient
Expand Down
45 changes: 42 additions & 3 deletions src/secops/chronicle/utils/request_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
#
"""Helper functions for Chronicle."""

import platform
from importlib.metadata import version as _metadata_version
from typing import TYPE_CHECKING, Any, Optional

import requests
from google.auth.exceptions import GoogleAuthError

from secops.exceptions import APIError
from secops.chronicle.models import APIVersion
from secops.exceptions import APIError

_LIBRARY_VERSION = _metadata_version("secops")

if TYPE_CHECKING:
from secops.chronicle.client import ChronicleClient
Expand All @@ -30,6 +34,31 @@
MAX_BODY_CHARS = 2000


def _build_api_client_header(endpoint_path: str) -> str:
"""Build the x-goog-api-client header value for a request.

Constructs a space-separated token string following the Google API client
header convention. A leading ':' is stripped from RPC-style endpoint paths
(e.g. ':udmSearch' becomes 'udmSearch').

Args:
endpoint_path: The API endpoint path passed to the request function,
e.g. 'rules/rule123:copy' or ':udmSearch'.

Returns:
Header value string in the format:
'gl-python/{version} rest/requests@{version} secops-wrapper/{version}
api/{endpoint}'.
"""
endpoint = endpoint_path.lstrip(":")
return (
f"gl-python/{platform.python_version()}"
f" rest/requests@{requests.__version__}"
f" secops-wrapper/{_LIBRARY_VERSION}"
f" api/{endpoint}"
)


def _safe_body_preview(text: str | None, limit: int = MAX_BODY_CHARS) -> str:
"""Generate a safe, truncated preview of body contents for error messages.

Expand Down Expand Up @@ -242,6 +271,12 @@ def chronicle_request(
else:
url = f'{base}/{endpoint_path.lstrip("/")}'

# Merge x-goog-api-client with any caller-supplied headers.
# Caller-supplied values take precedence.
merged_headers = {"x-goog-api-client": _build_api_client_header(endpoint_path)}
if headers:
merged_headers.update(headers)

# init request response
response = None

Expand All @@ -251,7 +286,7 @@ def chronicle_request(
url=url,
params=params,
json=json,
headers=headers,
headers=merged_headers,
timeout=timeout,
)
except GoogleAuthError as exc:
Expand Down Expand Up @@ -357,12 +392,16 @@ def chronicle_request_bytes(
else:
url = f'{base}/{endpoint_path.lstrip("/")}'

merged_headers = {"x-goog-api-client": _build_api_client_header(endpoint_path)}
if headers:
merged_headers.update(headers)

try:
response = client.session.request(
method=method,
url=url,
params=params,
headers=headers,
headers=merged_headers,
timeout=timeout,
stream=True,
)
Expand Down
14 changes: 7 additions & 7 deletions tests/chronicle/test_dashboard_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#
"""Tests for the Dashboard query module."""
import json
from unittest.mock import Mock, patch
from unittest.mock import ANY, Mock, patch

import pytest

Expand Down Expand Up @@ -102,7 +102,7 @@ def test_execute_query_success(
url=url,
params=None,
json=payload,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -138,7 +138,7 @@ def test_execute_query_with_filters(
url=url,
params=None,
json=payload,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -172,7 +172,7 @@ def test_execute_query_with_clear_cache(
url=url,
params=None,
json=payload,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -201,7 +201,7 @@ def test_execute_query_with_string_json(
),
params=None,
json={"query": {"query": query, "input": json.loads(interval_str)}},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -254,7 +254,7 @@ def test_get_execute_query_success(
url=url,
params=None,
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -291,7 +291,7 @@ def test_get_execute_query_with_full_id(
url=url,
params=None,
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down
31 changes: 16 additions & 15 deletions tests/chronicle/test_data_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest
from unittest.mock import (
ANY,
Mock,
patch,
call,
Expand Down Expand Up @@ -89,7 +90,7 @@ def test_create_data_table_success(
}
],
},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -195,7 +196,7 @@ def test_create_data_table_with_entity_mapping(
}
],
},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -271,7 +272,7 @@ def test_create_data_table_with_column_options(
},
],
},
headers=None,
headers=ANY,
timeout=None,
)

Expand All @@ -295,7 +296,7 @@ def test_get_data_table_success(self, mock_chronicle_client: Mock) -> None:
url=f"{mock_chronicle_client.base_url}/{mock_chronicle_client.instance_id}/dataTables/{dt_name}",
params=None,
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -325,7 +326,7 @@ def test_list_data_tables_success(
url=f"{mock_chronicle_client.base_url}/{mock_chronicle_client.instance_id}/dataTables",
params={"pageSize": 1000, "orderBy": "createTime asc"},
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -365,7 +366,7 @@ def test_delete_data_table_success(
url=f"{mock_chronicle_client.base_url}/{mock_chronicle_client.instance_id}/dataTables/{dt_name}",
params={"force": "true"},
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -427,7 +428,7 @@ def test_list_data_table_rows_success(
url=f"{mock_chronicle_client.base_url}/{mock_chronicle_client.instance_id}/dataTables/{dt_name}/dataTableRows",
params={"pageSize": 1000, "orderBy": "createTime asc"},
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -508,7 +509,7 @@ def test_create_reference_list_success(
"entries": [{"value": "entryA"}, {"value": "entryB"}],
"syntaxType": syntax_type.value,
},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -575,7 +576,7 @@ def test_get_reference_list_full_view_success(
url=f"{mock_chronicle_client.base_url(APIVersion.V1)}/{mock_chronicle_client.instance_id}/referenceLists/{rl_name}",
params={"view": ReferenceListView.FULL.value},
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -610,7 +611,7 @@ def test_list_reference_lists_basic_view_success(
url=f"{mock_chronicle_client.base_url(APIVersion.V1)}/{mock_chronicle_client.instance_id}/referenceLists",
params={"pageSize": 1000, "view": ReferenceListView.BASIC.value},
json=None,
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -666,7 +667,7 @@ def test_update_reference_list_success(
{"value": "new_entryY"},
],
},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -724,7 +725,7 @@ def test_update_data_table_success_both_params(
"description": new_description,
"row_time_to_live": new_row_ttl,
},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -763,7 +764,7 @@ def test_update_data_table_description_only(
url=f"{mock_chronicle_client.base_url}/{mock_chronicle_client.instance_id}/dataTables/{dt_name}",
params=None,
json={"description": new_description},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -802,7 +803,7 @@ def test_update_data_table_row_ttl_only(
url=f"{mock_chronicle_client.base_url}/{mock_chronicle_client.instance_id}/dataTables/{dt_name}",
params=None,
json={"row_time_to_live": new_row_ttl},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down Expand Up @@ -851,7 +852,7 @@ def test_update_data_table_with_update_mask(
"description": new_description,
"row_time_to_live": new_row_ttl,
},
headers=None,
headers=ANY,
timeout=None,
)

Expand Down
Loading