diff --git a/gooddata-sdk/gooddata_sdk/__init__.py b/gooddata-sdk/gooddata_sdk/__init__.py index bc8c0533a..42074fb62 100644 --- a/gooddata-sdk/gooddata_sdk/__init__.py +++ b/gooddata-sdk/gooddata_sdk/__init__.py @@ -25,6 +25,7 @@ from gooddata_sdk.catalog.data_source.validation.data_source import DataSourceValidator from gooddata_sdk.catalog.entity import BasicCredentials, TokenCredentialsFromFile from gooddata_sdk.catalog.organization.service import CatalogOrganizationService +from gooddata_sdk.catalog.permissions.declarative_model.permission import CatalogDeclarativeWorkspacePermissions from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import ( CatalogDeclarativeWorkspaceDataFilters, CatalogDeclarativeWorkspaceModel, diff --git a/gooddata-sdk/gooddata_sdk/catalog/base.py b/gooddata-sdk/gooddata_sdk/catalog/base.py index 7883a1696..cd8f2d165 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/base.py +++ b/gooddata-sdk/gooddata_sdk/catalog/base.py @@ -9,6 +9,16 @@ T = TypeVar("T", bound="Base") +def value_in_allowed(instance: Type[Base], attribute: attr.Attribute, value: str) -> None: + client_class = instance.client_class() + allowed_values = client_class.allowed_values.get((attribute.name,)) + if allowed_values is not None and value not in list(allowed_values.values()): + raise ValueError( + f"Allowed values for attribute {attribute.name} are: {', '.join(list(allowed_values.values()))}. " + f"But value {value} was passed." + ) + + @attr.s class Base: @classmethod diff --git a/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py b/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py index ad5dd51fc..82079bb6d 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py +++ b/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py @@ -12,7 +12,7 @@ from gooddata_sdk.catalog.base import Base from gooddata_sdk.catalog.data_source.declarative_model.physical_model.pdm import CatalogDeclarativeTables from gooddata_sdk.catalog.entity import TokenCredentialsFromFile -from gooddata_sdk.catalog.permissions.permission import CatalogDeclarativeDataSourcePermission +from gooddata_sdk.catalog.permissions.declarative_model.permission import CatalogDeclarativeDataSourcePermission from gooddata_sdk.utils import create_directory, read_layout_from_file, write_layout_to_file BIGQUERY_TYPE = "BIGQUERY" diff --git a/gooddata-sdk/gooddata_sdk/catalog/identifier.py b/gooddata-sdk/gooddata_sdk/catalog/identifier.py index ff6aff74e..219ea5dd3 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/identifier.py +++ b/gooddata-sdk/gooddata_sdk/catalog/identifier.py @@ -11,7 +11,7 @@ from gooddata_metadata_client.model.reference_identifier import ReferenceIdentifier from gooddata_metadata_client.model.user_group_identifier import UserGroupIdentifier from gooddata_metadata_client.model.workspace_identifier import WorkspaceIdentifier -from gooddata_sdk.catalog.base import Base +from gooddata_sdk.catalog.base import Base, value_in_allowed @attr.s(auto_attribs=True, kw_only=True) @@ -35,7 +35,7 @@ def client_class() -> Type[ReferenceIdentifier]: @attr.s(auto_attribs=True, kw_only=True) class CatalogGrainIdentifier(Base): id: str - type: str + type: str = attr.field(validator=value_in_allowed) @staticmethod def client_class() -> Type[GrainIdentifier]: @@ -45,7 +45,7 @@ def client_class() -> Type[GrainIdentifier]: @attr.s(auto_attribs=True, kw_only=True) class CatalogAssigneeIdentifier(Base): id: str - type: str + type: str = attr.field(validator=value_in_allowed) @staticmethod def client_class() -> Type[AssigneeIdentifier]: @@ -55,7 +55,7 @@ def client_class() -> Type[AssigneeIdentifier]: @attr.s(auto_attribs=True, kw_only=True) class CatalogUserGroupIdentifier(Base): id: str - type: str = "userGroup" + type: str = attr.field(validator=value_in_allowed) @staticmethod def client_class() -> Type[UserGroupIdentifier]: @@ -65,7 +65,7 @@ def client_class() -> Type[UserGroupIdentifier]: @attr.s(auto_attribs=True, kw_only=True) class CatalogLabelIdentifier(Base): id: str - type: str = "label" + type: str = attr.field(validator=value_in_allowed) @staticmethod def client_class() -> Type[LabelIdentifier]: diff --git a/gooddata-sdk/gooddata_sdk/catalog/permissions/declarative_model/__init__.py b/gooddata-sdk/gooddata_sdk/catalog/permissions/declarative_model/__init__.py new file mode 100644 index 000000000..67106a19b --- /dev/null +++ b/gooddata-sdk/gooddata_sdk/catalog/permissions/declarative_model/__init__.py @@ -0,0 +1 @@ +# (C) 2022 GoodData Corporation diff --git a/gooddata-sdk/gooddata_sdk/catalog/permissions/permission.py b/gooddata-sdk/gooddata_sdk/catalog/permissions/declarative_model/permission.py similarity index 89% rename from gooddata-sdk/gooddata_sdk/catalog/permissions/permission.py rename to gooddata-sdk/gooddata_sdk/catalog/permissions/declarative_model/permission.py index 3de394086..baab870ac 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/permissions/permission.py +++ b/gooddata-sdk/gooddata_sdk/catalog/permissions/declarative_model/permission.py @@ -11,13 +11,13 @@ DeclarativeWorkspaceHierarchyPermission, ) from gooddata_metadata_client.model.declarative_workspace_permissions import DeclarativeWorkspacePermissions -from gooddata_sdk.catalog.base import Base +from gooddata_sdk.catalog.base import Base, value_in_allowed from gooddata_sdk.catalog.identifier import CatalogAssigneeIdentifier @attr.s(auto_attribs=True, kw_only=True) class CatalogDeclarativeSingleWorkspacePermission(Base): - name: str + name: str = attr.field(validator=value_in_allowed) assignee: CatalogAssigneeIdentifier @staticmethod @@ -27,7 +27,7 @@ def client_class() -> Type[DeclarativeSingleWorkspacePermission]: @attr.s(auto_attribs=True, kw_only=True) class CatalogDeclarativeWorkspaceHierarchyPermission(Base): - name: str + name: str = attr.field(validator=value_in_allowed) assignee: CatalogAssigneeIdentifier @staticmethod @@ -37,7 +37,7 @@ def client_class() -> Type[DeclarativeWorkspaceHierarchyPermission]: @attr.s(auto_attribs=True, kw_only=True) class CatalogDeclarativeDataSourcePermission(Base): - name: str + name: str = attr.field(validator=value_in_allowed) assignee: CatalogAssigneeIdentifier @staticmethod diff --git a/gooddata-sdk/gooddata_sdk/catalog/permissions/service.py b/gooddata-sdk/gooddata_sdk/catalog/permissions/service.py new file mode 100644 index 000000000..59d26d71f --- /dev/null +++ b/gooddata-sdk/gooddata_sdk/catalog/permissions/service.py @@ -0,0 +1,19 @@ +# (C) 2022 GoodData Corporation +from gooddata_sdk import GoodDataApiClient +from gooddata_sdk.catalog.catalog_service_base import CatalogServiceBase +from gooddata_sdk.catalog.permissions.declarative_model.permission import CatalogDeclarativeWorkspacePermissions + + +class CatalogPermissionService(CatalogServiceBase): + def __init__(self, api_client: GoodDataApiClient) -> None: + super(CatalogPermissionService, self).__init__(api_client) + + def get_declarative_permissions(self, workspace_id: str) -> CatalogDeclarativeWorkspacePermissions: + return CatalogDeclarativeWorkspacePermissions.from_api(self._layout_api.get_workspace_permissions(workspace_id)) + + def set_declarative_permissions( + self, workspace_id: str, declarative_workspace_permissions: CatalogDeclarativeWorkspacePermissions + ) -> None: + self._layout_api.set_workspace_permissions( + workspace_id=workspace_id, declarative_workspace_permissions=declarative_workspace_permissions.to_api() + ) diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py index 91d02a255..562c47667 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py @@ -16,7 +16,7 @@ from gooddata_metadata_client.model.declarative_workspaces import DeclarativeWorkspaces from gooddata_sdk.catalog.base import Base from gooddata_sdk.catalog.identifier import CatalogWorkspaceIdentifier -from gooddata_sdk.catalog.permissions.permission import ( +from gooddata_sdk.catalog.permissions.declarative_model.permission import ( CatalogDeclarativeSingleWorkspacePermission, CatalogDeclarativeWorkspaceHierarchyPermission, ) diff --git a/gooddata-sdk/gooddata_sdk/sdk.py b/gooddata-sdk/gooddata_sdk/sdk.py index 1e4380420..42618382f 100644 --- a/gooddata-sdk/gooddata_sdk/sdk.py +++ b/gooddata-sdk/gooddata_sdk/sdk.py @@ -5,6 +5,7 @@ from gooddata_sdk.catalog.data_source.service import CatalogDataSourceService from gooddata_sdk.catalog.organization.service import CatalogOrganizationService +from gooddata_sdk.catalog.permissions.service import CatalogPermissionService from gooddata_sdk.catalog.user.service import CatalogUserService from gooddata_sdk.catalog.workspace.service import CatalogWorkspaceContentService, CatalogWorkspaceService from gooddata_sdk.client import GoodDataApiClient @@ -53,6 +54,7 @@ def __init__(self, client: GoodDataApiClient) -> None: self._insights = InsightService(self._client) self._tables = TableService(self._client) self._support = SupportService(self._client) + self._catalog_permission = CatalogPermissionService(self._client) @property def catalog_workspace(self) -> CatalogWorkspaceService: @@ -89,3 +91,7 @@ def support(self) -> SupportService: @property def catalog_user(self) -> CatalogUserService: return self._catalog_user + + @property + def catalog_permission(self) -> CatalogPermissionService: + return self._catalog_permission diff --git a/gooddata-sdk/tests/catalog/expected/declarative_workspace_permissions.json b/gooddata-sdk/tests/catalog/expected/declarative_workspace_permissions.json new file mode 100644 index 000000000..00ce93201 --- /dev/null +++ b/gooddata-sdk/tests/catalog/expected/declarative_workspace_permissions.json @@ -0,0 +1,34 @@ +{ + "hierarchyPermissions": [ + { + "assignee": { + "id": "demo2", + "type": "user" + }, + "name": "MANAGE" + }, + { + "assignee": { + "id": "demoGroup", + "type": "userGroup" + }, + "name": "ANALYZE" + } + ], + "permissions": [ + { + "assignee": { + "id": "demo2", + "type": "user" + }, + "name": "ANALYZE" + }, + { + "assignee": { + "id": "demoGroup", + "type": "userGroup" + }, + "name": "VIEW" + } + ] +} diff --git a/gooddata-sdk/tests/catalog/fixtures/permissions/get_declarative_permissions.json b/gooddata-sdk/tests/catalog/fixtures/permissions/get_declarative_permissions.json new file mode 100644 index 000000000..2ee6b3b03 --- /dev/null +++ b/gooddata-sdk/tests/catalog/fixtures/permissions/get_declarative_permissions.json @@ -0,0 +1,181 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo/permissions", + "body": null, + "headers": { + "Accept": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "" + }, + "headers": { + "GoodData-Deployment": [ + "aio" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Connection": [ + "keep-alive" + ], + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-GDC-TRACE-ID": [ + "7966d5896dd4991b" + ], + "Pragma": [ + "no-cache" + ], + "X-Frame-Options": [ + "DENY" + ], + "Content-Type": [ + "application/json" + ], + "Expires": [ + "0" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Date": [ + "Tue, 21 Jun 2022 08:40:13 GMT" + ], + "Server": [ + "nginx" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ] + }, + "body": { + "string": "{\"hierarchyPermissions\":[{\"assignee\":{\"id\":\"demo2\",\"type\":\"user\"},\"name\":\"MANAGE\"},{\"assignee\":{\"id\":\"demoGroup\",\"type\":\"userGroup\"},\"name\":\"ANALYZE\"}],\"permissions\":[{\"assignee\":{\"id\":\"demo2\",\"type\":\"user\"},\"name\":\"ANALYZE\"},{\"assignee\":{\"id\":\"demoGroup\",\"type\":\"userGroup\"},\"name\":\"VIEW\"}]}" + } + } + }, + { + "request": { + "method": "GET", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo/permissions", + "body": null, + "headers": { + "Accept": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "" + }, + "headers": { + "GoodData-Deployment": [ + "aio" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Connection": [ + "keep-alive" + ], + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "X-GDC-TRACE-ID": [ + "32301a35f61f6c8d" + ], + "Pragma": [ + "no-cache" + ], + "X-Frame-Options": [ + "DENY" + ], + "Content-Type": [ + "application/json" + ], + "Expires": [ + "0" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Date": [ + "Tue, 21 Jun 2022 08:40:13 GMT" + ], + "Server": [ + "nginx" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ] + }, + "body": { + "string": "{\"hierarchyPermissions\":[{\"assignee\":{\"id\":\"demo2\",\"type\":\"user\"},\"name\":\"MANAGE\"},{\"assignee\":{\"id\":\"demoGroup\",\"type\":\"userGroup\"},\"name\":\"ANALYZE\"}],\"permissions\":[{\"assignee\":{\"id\":\"demo2\",\"type\":\"user\"},\"name\":\"ANALYZE\"},{\"assignee\":{\"id\":\"demoGroup\",\"type\":\"userGroup\"},\"name\":\"VIEW\"}]}" + } + } + } + ] +} diff --git a/gooddata-sdk/tests/catalog/fixtures/permissions/put_declarative_permissions.json b/gooddata-sdk/tests/catalog/fixtures/permissions/put_declarative_permissions.json new file mode 100644 index 000000000..0fc724978 --- /dev/null +++ b/gooddata-sdk/tests/catalog/fixtures/permissions/put_declarative_permissions.json @@ -0,0 +1,433 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo_west/permissions", + "body": null, + "headers": { + "Accept": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "" + }, + "headers": { + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Date": [ + "Wed, 22 Jun 2022 06:45:47 GMT" + ], + "GoodData-Deployment": [ + "aio" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-GDC-TRACE-ID": [ + "6616b195e4efcac2" + ], + "Expires": [ + "0" + ], + "X-Frame-Options": [ + "DENY" + ], + "Connection": [ + "keep-alive" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Pragma": [ + "no-cache" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Content-Type": [ + "application/json" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Server": [ + "nginx" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ] + }, + "body": { + "string": "{\"hierarchyPermissions\":[],\"permissions\":[]}" + } + } + }, + { + "request": { + "method": "PUT", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo_west/permissions", + "body": "{\"permissions\": [{\"assignee\": {\"id\": \"demo2\", \"type\": \"user\"}, \"name\": \"ANALYZE\"}, {\"assignee\": {\"id\": \"demoGroup\", \"type\": \"userGroup\"}, \"name\": \"VIEW\"}], \"hierarchyPermissions\": [{\"assignee\": {\"id\": \"demo2\", \"type\": \"user\"}, \"name\": \"MANAGE\"}, {\"assignee\": {\"id\": \"demoGroup\", \"type\": \"userGroup\"}, \"name\": \"ANALYZE\"}]}", + "headers": { + "Content-Type": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 204, + "message": "" + }, + "headers": { + "X-Content-Type-Options": [ + "nosniff" + ], + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Date": [ + "Wed, 22 Jun 2022 06:45:48 GMT" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Pragma": [ + "no-cache" + ], + "X-GDC-TRACE-ID": [ + "ca7b7497b21f7cc5" + ], + "Expires": [ + "0" + ], + "X-Frame-Options": [ + "DENY" + ], + "Connection": [ + "keep-alive" + ], + "GoodData-Deployment": [ + "aio" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Server": [ + "nginx" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ] + }, + "body": { + "string": "" + } + } + }, + { + "request": { + "method": "GET", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo_west/permissions", + "body": null, + "headers": { + "Accept": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "" + }, + "headers": { + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Date": [ + "Wed, 22 Jun 2022 06:45:48 GMT" + ], + "GoodData-Deployment": [ + "aio" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-GDC-TRACE-ID": [ + "f3108fb08a0c58d5" + ], + "Expires": [ + "0" + ], + "X-Frame-Options": [ + "DENY" + ], + "Connection": [ + "keep-alive" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Pragma": [ + "no-cache" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Content-Type": [ + "application/json" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Server": [ + "nginx" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ] + }, + "body": { + "string": "{\"hierarchyPermissions\":[{\"assignee\":{\"id\":\"demo2\",\"type\":\"user\"},\"name\":\"MANAGE\"},{\"assignee\":{\"id\":\"demoGroup\",\"type\":\"userGroup\"},\"name\":\"ANALYZE\"}],\"permissions\":[{\"assignee\":{\"id\":\"demo2\",\"type\":\"user\"},\"name\":\"ANALYZE\"},{\"assignee\":{\"id\":\"demoGroup\",\"type\":\"userGroup\"},\"name\":\"VIEW\"}]}" + } + } + }, + { + "request": { + "method": "PUT", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo_west/permissions", + "body": "{\"permissions\": [], \"hierarchyPermissions\": []}", + "headers": { + "Content-Type": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 204, + "message": "" + }, + "headers": { + "X-Content-Type-Options": [ + "nosniff" + ], + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Date": [ + "Wed, 22 Jun 2022 06:45:48 GMT" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Pragma": [ + "no-cache" + ], + "X-GDC-TRACE-ID": [ + "05e2e82cf6c670c2" + ], + "Expires": [ + "0" + ], + "X-Frame-Options": [ + "DENY" + ], + "Connection": [ + "keep-alive" + ], + "GoodData-Deployment": [ + "aio" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Server": [ + "nginx" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ] + }, + "body": { + "string": "" + } + } + }, + { + "request": { + "method": "GET", + "uri": "http://localhost:3000/api/v1/layout/workspaces/demo_west/permissions", + "body": null, + "headers": { + "Accept": [ + "application/json" + ], + "X-Requested-With": [ + "XMLHttpRequest" + ], + "X-GDC-VALIDATE-RELATIONS": [ + "true" + ] + } + }, + "response": { + "status": { + "code": 200, + "message": "" + }, + "headers": { + "Permission-Policy": [ + "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment 'none';" + ], + "Date": [ + "Wed, 22 Jun 2022 06:45:48 GMT" + ], + "GoodData-Deployment": [ + "aio" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-GDC-TRACE-ID": [ + "32e03433e9752b8c" + ], + "Expires": [ + "0" + ], + "X-Frame-Options": [ + "DENY" + ], + "Connection": [ + "keep-alive" + ], + "Access-Control-Allow-Credentials": [ + "true" + ], + "Access-Control-Expose-Headers": [ + "Content-Disposition, Content-Length, Content-Range, Set-Cookie" + ], + "X-XSS-Protection": [ + "1; mode=block" + ], + "Vary": [ + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Pragma": [ + "no-cache" + ], + "Set-Cookie": [ + "SPRING_SEC_SECURITY_CONTEXT=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly" + ], + "Content-Type": [ + "application/json" + ], + "Cache-Control": [ + "no-cache, no-store, max-age=0, must-revalidate" + ], + "Server": [ + "nginx" + ], + "Content-Security-Policy": [ + "default-src 'self' *.wistia.com *.wistia.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wistia.com *.wistia.net src.litix.io matomo.anywhere.gooddata.com code.jquery.com unpkg.com cdn.jsdelivr.net cdnjs.cloudflare.com; img-src 'self' data: blob: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net privacy-policy.truste.com www.gooddata.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net fast.fonts.net; font-src 'self' data: fonts.gstatic.com *.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src 'self'; object-src 'none'; worker-src 'self' blob:; child-src blob:; connect-src 'self' *.tiles.mapbox.com *.mapbox.com *.litix.io *.wistia.com embedwistia-a.akamaihd.net matomo.anywhere.gooddata.com; media-src 'self' blob: data: *.wistia.com *.wistia.net embedwistia-a.akamaihd.net" + ] + }, + "body": { + "string": "{\"hierarchyPermissions\":[],\"permissions\":[]}" + } + } + } + ] +} diff --git a/gooddata-sdk/tests/catalog/test_catalog_permission.py b/gooddata-sdk/tests/catalog/test_catalog_permission.py new file mode 100644 index 000000000..7e42aaf72 --- /dev/null +++ b/gooddata-sdk/tests/catalog/test_catalog_permission.py @@ -0,0 +1,111 @@ +# (C) 2022 GoodData Corporation +from __future__ import annotations + +import json +from pathlib import Path + +import pytest +import vcr + +import gooddata_metadata_client.apis as metadata_apis +from gooddata_sdk import GoodDataApiClient, GoodDataSdk +from gooddata_sdk.catalog.identifier import CatalogAssigneeIdentifier +from gooddata_sdk.catalog.permissions.declarative_model.permission import ( + CatalogDeclarativeDataSourcePermission, + CatalogDeclarativeSingleWorkspacePermission, + CatalogDeclarativeWorkspaceHierarchyPermission, + CatalogDeclarativeWorkspacePermissions, +) +from tests import VCR_MATCH_ON + +_current_dir = Path(__file__).parent.absolute() +_fixtures_dir = _current_dir / "fixtures" / "permissions" + +gd_vcr = vcr.VCR(filter_headers=["authorization", "user-agent"], serializer="json", match_on=VCR_MATCH_ON) + + +def _empty_permissions(sdk: GoodDataSdk, workspace_id: str) -> None: + empty_permissions_e = CatalogDeclarativeWorkspacePermissions(permissions=[], hierarchy_permissions=[]) + sdk.catalog_permission.set_declarative_permissions( + workspace_id=workspace_id, declarative_workspace_permissions=empty_permissions_e + ) + empty_permissions_o = sdk.catalog_permission.get_declarative_permissions(workspace_id=workspace_id) + assert empty_permissions_e == empty_permissions_o + assert empty_permissions_e.to_dict(camel_case=True) == empty_permissions_o.to_dict(camel_case=True) + + +def _assert_default_permissions(catalog_declarative_permissions: CatalogDeclarativeWorkspacePermissions) -> None: + assert len(catalog_declarative_permissions.hierarchy_permissions) == 2 + assert set( + hierarchy_permission.assignee.id + for hierarchy_permission in catalog_declarative_permissions.hierarchy_permissions + ) == {"demo2", "demoGroup"} + assert set( + hierarchy_permission.name for hierarchy_permission in catalog_declarative_permissions.hierarchy_permissions + ) == {"MANAGE", "ANALYZE"} + assert len(catalog_declarative_permissions.permissions) == 2 + assert set(permission.assignee.id for permission in catalog_declarative_permissions.permissions) == { + "demo2", + "demoGroup", + } + assert set(permission.name for permission in catalog_declarative_permissions.permissions) == {"ANALYZE", "VIEW"} + + +def _validation_helper(class_type, attribute_name: str): + client_class = class_type.client_class() + allowed_values = list(client_class.allowed_values.get((attribute_name,)).values()) + for allowed_value in allowed_values: + try: + class_type(name=allowed_value, assignee=CatalogAssigneeIdentifier(id="", type="user")) + except ValueError: + assert False + with pytest.raises(ValueError): + class_type(name="nonsense", assignee=CatalogAssigneeIdentifier(id="", type="user")) + + +def test_single_workspace_permission_validation(test_config): + _validation_helper(CatalogDeclarativeSingleWorkspacePermission, "name") + + +def test_workspace_hierarchy_permission(test_config): + _validation_helper(CatalogDeclarativeWorkspaceHierarchyPermission, "name") + + +def test_data_source_permission(test_config): + _validation_helper(CatalogDeclarativeDataSourcePermission, "name") + + +@gd_vcr.use_cassette(str(_fixtures_dir / "get_declarative_permissions.json")) +def test_get_declarative_permissions(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + client = GoodDataApiClient(host=test_config["host"], token=test_config["token"]) + layout_api = metadata_apis.LayoutApi(client.metadata_client) + + catalog_declarative_permissions = sdk.catalog_permission.get_declarative_permissions(test_config["workspace"]) + declarative_permissions = layout_api.get_workspace_permissions(test_config["workspace"]) + _assert_default_permissions(catalog_declarative_permissions) + assert catalog_declarative_permissions.to_dict(camel_case=True) == declarative_permissions.to_dict(camel_case=True) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "put_declarative_permissions.json")) +def test_put_declarative_permissions(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + expected_json_path = _current_dir / "expected" / "declarative_workspace_permissions.json" + workspace_id = test_config["workspace_with_parent"] + declarative_permissions_e = sdk.catalog_permission.get_declarative_permissions(workspace_id) + assert len(declarative_permissions_e.permissions) == 0 + assert len(declarative_permissions_e.hierarchy_permissions) == 0 + + with open(expected_json_path, "r", encoding="utf-8") as f: + data = json.load(f) + + declarative_workspace_permissions = CatalogDeclarativeWorkspacePermissions.from_dict(data, camel_case=True) + + try: + sdk.catalog_permission.set_declarative_permissions( + workspace_id=workspace_id, declarative_workspace_permissions=declarative_workspace_permissions + ) + declarative_permissions_o = sdk.catalog_permission.get_declarative_permissions(workspace_id=workspace_id) + _assert_default_permissions(declarative_permissions_o) + finally: + _empty_permissions(sdk, workspace_id)