LakeFormation: extend functionality of list_permissions (#7051)
This commit is contained in:
parent
3eed5c2d68
commit
7c5f0ef7a3
@ -123,7 +123,14 @@ class ListPermissionsResource:
|
|||||||
lf_tag: Optional[ListPermissionsResourceLFTag],
|
lf_tag: Optional[ListPermissionsResourceLFTag],
|
||||||
lf_tag_policy: Optional[ListPermissionsResourceLFTagPolicy],
|
lf_tag_policy: Optional[ListPermissionsResourceLFTagPolicy],
|
||||||
):
|
):
|
||||||
if catalog is None and database is None and table is None:
|
if (
|
||||||
|
catalog is None
|
||||||
|
and database is None
|
||||||
|
and table is None
|
||||||
|
and data_location is None
|
||||||
|
):
|
||||||
|
# Error message is the exact string returned by the AWS-CLI eventhough it is valid
|
||||||
|
# to not populate the respective fields as long as data_location is given.
|
||||||
raise InvalidInput(
|
raise InvalidInput(
|
||||||
"Resource must have either the catalog, table or database field populated."
|
"Resource must have either the catalog, table or database field populated."
|
||||||
)
|
)
|
||||||
@ -276,10 +283,11 @@ class LakeFormationBackend(BaseBackend):
|
|||||||
only matching permissions with resource-type "Database" are returned;
|
only matching permissions with resource-type "Database" are returned;
|
||||||
if catalog and database are not provided and table is provided:
|
if catalog and database are not provided and table is provided:
|
||||||
only matching permissions with resource-type "Table" are returned;
|
only matching permissions with resource-type "Table" are returned;
|
||||||
|
if catalog and database and table are not provided and data location is provided:
|
||||||
|
only matching permissions with resource-type "DataLocation" are returned;
|
||||||
"""
|
"""
|
||||||
if resource is None: # Check for linter
|
if resource is None: # Check for linter
|
||||||
return False
|
return False
|
||||||
|
|
||||||
permission_resource = permission["Resource"]
|
permission_resource = permission["Resource"]
|
||||||
catalog = resource.catalog
|
catalog = resource.catalog
|
||||||
if catalog is not None and "Catalog" in permission_resource:
|
if catalog is not None and "Catalog" in permission_resource:
|
||||||
@ -291,7 +299,7 @@ class LakeFormationBackend(BaseBackend):
|
|||||||
if database.catalog_id is not None:
|
if database.catalog_id is not None:
|
||||||
equals = equals and (
|
equals = equals and (
|
||||||
database.catalog_id
|
database.catalog_id
|
||||||
== permission_resource["Database"]["CatalogId"]
|
== permission_resource["Database"].get("CatalogId")
|
||||||
)
|
)
|
||||||
return equals
|
return equals
|
||||||
|
|
||||||
@ -302,7 +310,8 @@ class LakeFormationBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
if table.catalog_id is not None:
|
if table.catalog_id is not None:
|
||||||
equals = equals and (
|
equals = equals and (
|
||||||
table.catalog_id == permission_resource["Table"]["CatalogId"]
|
table.catalog_id
|
||||||
|
== permission_resource["Table"].get("CatalogId")
|
||||||
)
|
)
|
||||||
if table.name is not None and table.table_wildcard is None:
|
if table.name is not None and table.table_wildcard is None:
|
||||||
equals = equals and (
|
equals = equals and (
|
||||||
@ -314,6 +323,20 @@ class LakeFormationBackend(BaseBackend):
|
|||||||
== permission_resource["Table"]["TableWildcard"]
|
== permission_resource["Table"]["TableWildcard"]
|
||||||
)
|
)
|
||||||
return equals
|
return equals
|
||||||
|
|
||||||
|
data_location = resource.data_location
|
||||||
|
if data_location is not None and "DataLocation" in permission_resource:
|
||||||
|
equals = (
|
||||||
|
data_location.resource_arn
|
||||||
|
== permission_resource["DataLocation"]["ResourceArn"]
|
||||||
|
)
|
||||||
|
if data_location.catalog_id is not None:
|
||||||
|
equals = equals and (
|
||||||
|
data_location.catalog_id
|
||||||
|
== permission_resource["DataLocation"].get("CatalogId")
|
||||||
|
)
|
||||||
|
return equals
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if resource is not None:
|
if resource is not None:
|
||||||
|
@ -8,10 +8,13 @@ from .models import (
|
|||||||
LakeFormationBackend,
|
LakeFormationBackend,
|
||||||
ListPermissionsResource,
|
ListPermissionsResource,
|
||||||
ListPermissionsResourceDatabase,
|
ListPermissionsResourceDatabase,
|
||||||
|
ListPermissionsResourceDataLocation,
|
||||||
ListPermissionsResourceTable,
|
ListPermissionsResourceTable,
|
||||||
RessourceType,
|
RessourceType,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .exceptions import InvalidInput
|
||||||
|
|
||||||
|
|
||||||
class LakeFormationResponse(BaseResponse):
|
class LakeFormationResponse(BaseResponse):
|
||||||
"""Handler for LakeFormation requests and responses."""
|
"""Handler for LakeFormation requests and responses."""
|
||||||
@ -97,6 +100,12 @@ class LakeFormationResponse(BaseResponse):
|
|||||||
principal = self._get_param("Principal")
|
principal = self._get_param("Principal")
|
||||||
resource = self._get_param("Resource")
|
resource = self._get_param("Resource")
|
||||||
resource_type_param = self._get_param("ResourceType")
|
resource_type_param = self._get_param("ResourceType")
|
||||||
|
if principal is not None and resource is None:
|
||||||
|
# Error message is the exact string returned by the AWS-CLI
|
||||||
|
raise InvalidInput(
|
||||||
|
"An error occurred (InvalidInputException) when calling the ListPermissions operation: Resource is mandatory if Principal is set in the input."
|
||||||
|
)
|
||||||
|
|
||||||
if resource_type_param is None:
|
if resource_type_param is None:
|
||||||
resource_type = None
|
resource_type = None
|
||||||
else:
|
else:
|
||||||
@ -108,6 +117,7 @@ class LakeFormationResponse(BaseResponse):
|
|||||||
database_sub_dictionary = resource.get("Database")
|
database_sub_dictionary = resource.get("Database")
|
||||||
table_sub_dictionary = resource.get("Table")
|
table_sub_dictionary = resource.get("Table")
|
||||||
catalog_sub_dictionary = resource.get("Catalog")
|
catalog_sub_dictionary = resource.get("Catalog")
|
||||||
|
data_location_sub_dictionary = resource.get("DataLocation")
|
||||||
|
|
||||||
if database_sub_dictionary is None:
|
if database_sub_dictionary is None:
|
||||||
database = None
|
database = None
|
||||||
@ -127,12 +137,20 @@ class LakeFormationResponse(BaseResponse):
|
|||||||
table_wildcard=table_sub_dictionary.get("TableWildcard"),
|
table_wildcard=table_sub_dictionary.get("TableWildcard"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if data_location_sub_dictionary is None:
|
||||||
|
data_location = None
|
||||||
|
else:
|
||||||
|
data_location = ListPermissionsResourceDataLocation(
|
||||||
|
resource_arn=data_location_sub_dictionary.get("ResourceArn"),
|
||||||
|
catalog_id=data_location_sub_dictionary.get("CatalogId"),
|
||||||
|
)
|
||||||
|
|
||||||
list_permission_resource = ListPermissionsResource(
|
list_permission_resource = ListPermissionsResource(
|
||||||
catalog=catalog_sub_dictionary,
|
catalog=catalog_sub_dictionary,
|
||||||
database=database,
|
database=database,
|
||||||
table=table,
|
table=table,
|
||||||
table_with_columns=None,
|
table_with_columns=None,
|
||||||
data_location=None,
|
data_location=data_location,
|
||||||
data_cells_filter=None,
|
data_cells_filter=None,
|
||||||
lf_tag=None,
|
lf_tag=None,
|
||||||
lf_tag_policy=None,
|
lf_tag_policy=None,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Unit tests for lakeformation-supported APIs."""
|
"""Unit tests for lakeformation-supported APIs."""
|
||||||
from typing import Dict
|
from typing import Dict, Optional
|
||||||
import boto3
|
import boto3
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -118,6 +118,24 @@ def test_list_permissions():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lakeformation
|
||||||
|
def test_list_permissions_invalid_input():
|
||||||
|
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||||
|
|
||||||
|
client.grant_permissions(
|
||||||
|
Principal={"DataLakePrincipalIdentifier": "asdf"},
|
||||||
|
Resource={"Database": {"Name": "db"}},
|
||||||
|
Permissions=["ALL"],
|
||||||
|
PermissionsWithGrantOption=["SELECT"],
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(client.exceptions.InvalidInputException):
|
||||||
|
client.list_permissions(Principal={"DataLakePrincipalIdentifier": "asdf"})
|
||||||
|
|
||||||
|
with pytest.raises(client.exceptions.InvalidInputException):
|
||||||
|
client.list_permissions(Resource={})
|
||||||
|
|
||||||
|
|
||||||
def grant_table_permissions(
|
def grant_table_permissions(
|
||||||
client: boto3.client, catalog_id: str, principal: str, db: str, table: str
|
client: boto3.client, catalog_id: str, principal: str, db: str, table: str
|
||||||
):
|
):
|
||||||
@ -159,6 +177,18 @@ def grant_catalog_permissions(client: boto3.client, catalog_id: str, principal:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def grant_data_location_permissions(
|
||||||
|
client: boto3.client, resource_arn: str, catalog_id: str, principal: str
|
||||||
|
):
|
||||||
|
client.grant_permissions(
|
||||||
|
CatalogId=catalog_id,
|
||||||
|
Principal={"DataLakePrincipalIdentifier": principal},
|
||||||
|
Resource={"DataLocation": {"ResourceArn": resource_arn}},
|
||||||
|
Permissions=["DATA_LOCATION_ACCESS"],
|
||||||
|
PermissionsWithGrantOption=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def db_response(principal: str, catalog_id: str, db: str) -> Dict:
|
def db_response(principal: str, catalog_id: str, db: str) -> Dict:
|
||||||
return {
|
return {
|
||||||
"Principal": {"DataLakePrincipalIdentifier": principal},
|
"Principal": {"DataLakePrincipalIdentifier": principal},
|
||||||
@ -198,6 +228,20 @@ def catalog_response(principal: str) -> Dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def data_location_response(
|
||||||
|
principal: str, resource_arn: str, catalog_id: Optional[str] = None
|
||||||
|
) -> Dict:
|
||||||
|
response = {
|
||||||
|
"Principal": {"DataLakePrincipalIdentifier": principal},
|
||||||
|
"Resource": {"DataLocation": {"ResourceArn": resource_arn}},
|
||||||
|
"Permissions": ["DATA_LOCATION_ACCESS"],
|
||||||
|
"PermissionsWithGrantOption": [],
|
||||||
|
}
|
||||||
|
if catalog_id is not None:
|
||||||
|
response["Resource"]["CatalogId"] = catalog_id
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@mock_lakeformation
|
@mock_lakeformation
|
||||||
def test_list_permissions_filtered_for_catalog_id():
|
def test_list_permissions_filtered_for_catalog_id():
|
||||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||||
@ -342,6 +386,45 @@ def test_list_permissions_filtered_for_resource_table():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_lakeformation
|
||||||
|
def test_list_permissions_filtered_for_resource_data_location():
|
||||||
|
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||||
|
catalog_id = "000000000000"
|
||||||
|
principal = "principal"
|
||||||
|
data_location_resource_arn_1 = "resource_arn_1"
|
||||||
|
data_location_resource_arn_2 = "resource_arn_2"
|
||||||
|
|
||||||
|
grant_data_location_permissions(
|
||||||
|
catalog_id=catalog_id,
|
||||||
|
client=client,
|
||||||
|
resource_arn=data_location_resource_arn_1,
|
||||||
|
principal=principal,
|
||||||
|
)
|
||||||
|
grant_data_location_permissions(
|
||||||
|
catalog_id=catalog_id,
|
||||||
|
client=client,
|
||||||
|
resource_arn=data_location_resource_arn_2,
|
||||||
|
principal=principal,
|
||||||
|
)
|
||||||
|
|
||||||
|
res = client.list_permissions(
|
||||||
|
CatalogId=catalog_id,
|
||||||
|
Resource={"DataLocation": {"ResourceArn": data_location_resource_arn_1}},
|
||||||
|
)
|
||||||
|
assert res["PrincipalResourcePermissions"] == [
|
||||||
|
data_location_response(principal, resource_arn=data_location_resource_arn_1)
|
||||||
|
]
|
||||||
|
|
||||||
|
res = client.list_permissions(
|
||||||
|
CatalogId=catalog_id,
|
||||||
|
Resource={"DataLocation": {"ResourceArn": data_location_resource_arn_2}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res["PrincipalResourcePermissions"] == [
|
||||||
|
data_location_response(principal, resource_arn=data_location_resource_arn_2)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@mock_lakeformation
|
@mock_lakeformation
|
||||||
def test_revoke_permissions():
|
def test_revoke_permissions():
|
||||||
client = boto3.client("lakeformation", region_name="eu-west-2")
|
client = boto3.client("lakeformation", region_name="eu-west-2")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user