Skip to content
Merged
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
9 changes: 9 additions & 0 deletions ddtrace/appsec/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ def __init__(
self.location = location.replace(APPSEC.SECURITY_RESPONSE_ID, security_response_id)
self.content_type: str = "application/json"

def get(self, method_name: str, default: Any = None) -> Any:
"""
Dictionary-like get method for backward compatibility with Lambda integration.

Returns the attribute value if it exists, otherwise returns the default value.
This allows Block_config to be used in contexts that expect dictionary-like access.
"""
return getattr(self, method_name, default)


class Telemetry_result:
__slots__ = [
Expand Down
111 changes: 111 additions & 0 deletions tests/appsec/appsec/test_appsec_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,114 @@ def test_get_blocked_template_user_file_exists_json():
assert utils._get_blocked_template("text/html", BLOCK_ID) == utils._format_template(
BLOCKED_RESPONSE_HTML, BLOCK_ID
)


def test_block_config_get_existing_attribute():
"""
Test that get() returns existing attribute values.

Regression test for PR #15042 which changed Block_config from dict to class,
breaking Lambda integration that expects .get() method for dictionary-like access.
"""
from ddtrace.appsec._utils import Block_config

config = Block_config(status_code=403, type="auto", grpc_status_code=10)

assert config.get("status_code") == 403
assert config.get("type") == "auto"
assert config.get("grpc_status_code") == 10


def test_block_config_get_nonexistent_attribute_default_none():
"""Test that get() returns None for nonexistent attributes when no default is provided."""
from ddtrace.appsec._utils import Block_config

config = Block_config()

assert config.get("nonexistent_field") is None
assert config.get("missing_attribute") is None


def test_block_config_get_nonexistent_attribute_with_custom_default():
"""Test that get() returns custom default for nonexistent attributes."""
from ddtrace.appsec._utils import Block_config

config = Block_config()

assert config.get("nonexistent_field", "default_value") == "default_value"
assert config.get("missing_attribute", 123) == 123
assert config.get("another_missing", False) is False


def test_block_config_get_all_standard_attributes():
"""Test that get() works for all standard Block_config attributes."""
from ddtrace.appsec._utils import Block_config

config = Block_config(
type="json",
status_code=404,
grpc_status_code=5,
security_response_id="custom-block-id",
location="/custom/location",
)

# Test all standard attributes
assert config.get("block_id") == "custom-block-id"
assert config.get("status_code") == 404
assert config.get("grpc_status_code") == 5
assert config.get("type") == "json"
assert config.get("content_type") == "application/json"
# Location should have the security_response_id replaced
assert "/custom/location" in config.get("location")


def test_block_config_get_method_lambda_compatibility():
"""
Test Lambda integration compatibility scenario.

This simulates the actual error from Lambda where code expects
dictionary-like access: block_config.get("key", default)

Reproduces the error:
AttributeError: 'Block_config' object has no attribute 'get'
"""
from ddtrace.appsec._utils import Block_config

# Simulate Lambda's get_asm_blocked_response usage
block_config = Block_config(
type="auto",
status_code=403,
grpc_status_code=10,
security_response_id="block-001",
)

# Lambda code does things like: block_config.get("status_code", 403)
status = block_config.get("status_code", 403)
assert status == 403

# Lambda code might check for optional fields
custom_field = block_config.get("custom_field", "default")
assert custom_field == "default"

# Verify block_id is accessible
block_id = block_config.get("block_id")
assert block_id == "block-001"


def test_block_config_get_preserves_attribute_access():
"""Test that adding get() doesn't break normal attribute access."""
from ddtrace.appsec._utils import Block_config

config = Block_config(status_code=500, type="html")

# Normal attribute access should still work
assert config.status_code == 500
assert config.type == "html"

# get() method should return the same values
assert config.get("status_code") == 500
assert config.get("type") == "html"

# Both access methods should return identical values
assert config.status_code == config.get("status_code")
assert config.type == config.get("type")
Loading