DynamoDB: add checks for projection expression (#5380)

This commit is contained in:
Amine Haj Ali 2022-08-12 20:45:14 +01:00 committed by GitHub
parent f91ffcffb8
commit 7386163a67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 14 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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")