DynamoDB: add checks for projection expression (#5380)
This commit is contained in:
parent
f91ffcffb8
commit
7386163a67
@ -1485,13 +1485,6 @@ class DynamoDBBackend(BaseBackend):
|
||||
filter_expression, expr_names, expr_values
|
||||
)
|
||||
|
||||
projection_expression = ",".join(
|
||||
[
|
||||
expr_names.get(attr, attr)
|
||||
for attr in projection_expression.replace(" ", "").split(",")
|
||||
]
|
||||
)
|
||||
|
||||
return table.scan(
|
||||
scan_filters,
|
||||
limit,
|
||||
|
@ -7,6 +7,7 @@ from functools import wraps
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from moto.core.utils import camelcase_to_underscores, amz_crc32, amzn_request_id
|
||||
from moto.dynamodb.parsing.reserved_keywords import ReservedKeywords
|
||||
from .exceptions import (
|
||||
MockValidationException,
|
||||
ResourceNotFoundException,
|
||||
@ -100,6 +101,21 @@ def put_has_empty_attrs(field_updates, table):
|
||||
return False
|
||||
|
||||
|
||||
def check_projection_expression(expression):
|
||||
if expression.upper() in ReservedKeywords.get_reserved_keywords():
|
||||
raise MockValidationException(
|
||||
f"ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: {expression}"
|
||||
)
|
||||
if expression[0].isnumeric():
|
||||
raise MockValidationException(
|
||||
"ProjectionExpression: Attribute name starts with a number"
|
||||
)
|
||||
if " " in expression:
|
||||
raise MockValidationException(
|
||||
"ProjectionExpression: Attribute name contains white space"
|
||||
)
|
||||
|
||||
|
||||
class DynamoHandler(BaseResponse):
|
||||
def get_endpoint_name(self, headers):
|
||||
"""Parses request headers and extracts part od the X-Amz-Target
|
||||
@ -728,14 +744,17 @@ class DynamoHandler(BaseResponse):
|
||||
else expression
|
||||
)
|
||||
|
||||
if projection_expression and expr_attr_names:
|
||||
if projection_expression:
|
||||
expressions = [x.strip() for x in projection_expression.split(",")]
|
||||
return ",".join(
|
||||
[
|
||||
".".join([_adjust(expr) for expr in nested_expr.split(".")])
|
||||
for nested_expr in expressions
|
||||
]
|
||||
)
|
||||
for expression in expressions:
|
||||
check_projection_expression(expression)
|
||||
if expr_attr_names:
|
||||
return ",".join(
|
||||
[
|
||||
".".join([_adjust(expr) for expr in nested_expr.split(".")])
|
||||
for nested_expr in expressions
|
||||
]
|
||||
)
|
||||
|
||||
return projection_expression
|
||||
|
||||
@ -760,6 +779,10 @@ class DynamoHandler(BaseResponse):
|
||||
limit = self.body.get("Limit")
|
||||
index_name = self.body.get("IndexName")
|
||||
|
||||
projection_expression = self._adjust_projection_expression(
|
||||
projection_expression, expression_attribute_names
|
||||
)
|
||||
|
||||
try:
|
||||
items, scanned_count, last_evaluated_key = self.dynamodb_backend.scan(
|
||||
name,
|
||||
|
@ -5780,3 +5780,63 @@ def test_projection_expression_execution_order():
|
||||
ProjectionExpression="#a",
|
||||
ExpressionAttributeNames={"#a": "hashKey"},
|
||||
)
|
||||
|
||||
|
||||
@mock_dynamodb
|
||||
def test_invalid_projection_expressions():
|
||||
table_name = "test-projection-expressions-table"
|
||||
client = boto3.client("dynamodb", region_name="us-east-1")
|
||||
client.create_table(
|
||||
TableName=table_name,
|
||||
KeySchema=[{"AttributeName": "customer", "KeyType": "HASH"}],
|
||||
AttributeDefinitions=[{"AttributeName": "customer", "AttributeType": "S"}],
|
||||
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ClientError,
|
||||
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
|
||||
):
|
||||
client.scan(TableName=table_name, ProjectionExpression="name")
|
||||
with pytest.raises(
|
||||
ClientError, match="ProjectionExpression: Attribute name starts with a number"
|
||||
):
|
||||
client.scan(TableName=table_name, ProjectionExpression="3ame")
|
||||
with pytest.raises(
|
||||
ClientError, match="ProjectionExpression: Attribute name contains white space"
|
||||
):
|
||||
client.scan(TableName=table_name, ProjectionExpression="na me")
|
||||
|
||||
with pytest.raises(
|
||||
ClientError,
|
||||
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
|
||||
):
|
||||
client.get_item(
|
||||
TableName=table_name,
|
||||
Key={"customer": {"S": "a"}},
|
||||
ProjectionExpression="name",
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ClientError,
|
||||
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
|
||||
):
|
||||
client.query(
|
||||
TableName=table_name,
|
||||
KeyConditionExpression="a",
|
||||
ProjectionExpression="name",
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ClientError,
|
||||
match="ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: name",
|
||||
):
|
||||
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, name")
|
||||
with pytest.raises(
|
||||
ClientError, match="ProjectionExpression: Attribute name starts with a number"
|
||||
):
|
||||
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, 3ame")
|
||||
with pytest.raises(
|
||||
ClientError, match="ProjectionExpression: Attribute name contains white space"
|
||||
):
|
||||
client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, na me")
|
||||
|
Loading…
Reference in New Issue
Block a user