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