From d94c5dee4ea536c7b7e87b9817676b844fb36291 Mon Sep 17 00:00:00 2001 From: marcos-silva-rodrigues Date: Tue, 6 May 2025 10:10:54 -0300 Subject: [PATCH] FEAT: init public routes to get data asset by public embed --- docsfera.json | 713 ++++++++---------- src/modules/auth/auth.controller.ts | 2 +- src/modules/catalog/catalog.module.ts | 7 +- src/modules/catalog/catalog.service.ts | 143 +++- .../{ => controllers}/catalog.controller.ts | 302 ++------ .../controllers/catalog.public.controller.ts | 184 +++++ .../connection-test.service.ts | 2 +- .../connection/connection.controller.ts | 2 +- src/modules/customers/customers.controller.ts | 2 +- src/modules/customers/customers.service.ts | 10 + .../network-policy.controller.ts | 2 +- src/modules/oauth/oauth.controller.ts | 2 +- src/modules/open-data/open-data.controller.ts | 2 +- .../pipelinesV2/pipelines.controller.ts | 2 +- src/modules/users/users.controller.ts | 2 +- ... PackTheMetadata.ts => PackTheMetadata.ts} | 0 16 files changed, 724 insertions(+), 653 deletions(-) rename src/modules/catalog/{ => controllers}/catalog.controller.ts (80%) create mode 100644 src/modules/catalog/controllers/catalog.public.controller.ts rename src/utils/{ PackTheMetadata.ts => PackTheMetadata.ts} (100%) diff --git a/docsfera.json b/docsfera.json index b8d9671..2fd799a 100644 --- a/docsfera.json +++ b/docsfera.json @@ -3873,9 +3873,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -3906,11 +3903,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - } ] } }, @@ -3947,9 +3939,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -3981,7 +3970,7 @@ } }, { - "name": "shared", + "name": "c", "required": true, "in": "query", "schema": { @@ -3996,14 +3985,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } ] }, "put": { @@ -4056,9 +4037,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4104,9 +4082,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4147,9 +4122,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4179,6 +4151,14 @@ "schema": { "type": "string" } + }, + { + "name": "c", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -4195,14 +4175,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } ] } }, @@ -4229,6 +4201,14 @@ "schema": { "type": "string" } + }, + { + "name": "c", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -4245,14 +4225,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } ] } }, @@ -4279,6 +4251,14 @@ "schema": { "type": "string" } + }, + { + "name": "c", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -4295,14 +4275,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } ] }, "post": { @@ -4345,9 +4317,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4386,11 +4355,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - } ] } }, @@ -4426,11 +4390,6 @@ }, "tags": [ "Catalog" - ], - "security": [ - { - "access-token": [] - } ] } }, @@ -4485,9 +4444,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4543,9 +4499,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4595,9 +4548,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4645,9 +4595,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4687,9 +4634,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4730,9 +4674,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4779,9 +4720,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4829,9 +4767,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4877,9 +4812,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4919,9 +4851,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4954,9 +4883,6 @@ "Catalog" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -4996,6 +4922,238 @@ "tags": [ "Catalog" ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, + "/customers/{id}/mfa": { + "post": { + "operationId": "CustomersController_enableMfaEnforce", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnforceMfa" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, + "/customers/{id}/links": { + "get": { + "operationId": "CustomersController_getCustomerLinks", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerLinksResponse" + } + } + } + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + } + ] + }, + "put": { + "operationId": "CustomersController_setCustomerLinks", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerLinkRequest" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, + "/customers/token": { + "get": { + "operationId": "CustomersController_getCustomerToken", + "parameters": [ + { + "name": "exp", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, + "/customers/monitoring-dashboard": { + "get": { + "operationId": "CustomersController_getCustomerMonitoringDashboard", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, + "/customers/logs-dashboard": { + "get": { + "operationId": "CustomersController_getCustomerMixPanelLogsDashboard", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, + "/customers/access-dashboard": { + "get": { + "operationId": "CustomersController_getAccessDashboard", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Customers" + ], "security": [ { "access-token": [] @@ -5519,241 +5677,6 @@ ] } }, - "/customers/{id}/mfa": { - "post": { - "operationId": "CustomersController_enableMfaEnforce", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EnforceMfa" - } - } - } - }, - "responses": { - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - }, - { - "access-token": [] - } - ] - } - }, - "/customers/{id}/links": { - "get": { - "operationId": "CustomersController_getCustomerLinks", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomerLinksResponse" - } - } - } - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - } - ] - }, - "put": { - "operationId": "CustomersController_setCustomerLinks", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomerLinkRequest" - } - } - } - }, - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } - ] - } - }, - "/customers/token": { - "get": { - "operationId": "CustomersController_getCustomerToken", - "parameters": [ - { - "name": "exp", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } - ] - } - }, - "/customers/monitoring-dashboard": { - "get": { - "operationId": "CustomersController_getCustomerMonitoringDashboard", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - } - ] - } - }, - "/customers/logs-dashboard": { - "get": { - "operationId": "CustomersController_getCustomerMixPanelLogsDashboard", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - }, - { - "access-token": [] - } - ] - } - }, - "/customers/access-dashboard": { - "get": { - "operationId": "CustomersController_getAccessDashboard", - "parameters": [], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Customers" - ], - "security": [ - { - "access-token": [] - }, - { - "access-token": [] - }, - { - "access-token": [] - } - ] - } - }, "/open-data/sharing-ocean-data": { "post": { "operationId": "OpenDataController_createUser", @@ -8435,6 +8358,67 @@ "updated_by" ] }, + "EnforceMfa": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + }, + "CustomerLink": { + "type": "object", + "properties": { + "href": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "iconSrc": { + "type": "string" + } + }, + "required": [ + "href", + "name", + "description" + ] + }, + "CustomerLinksResponse": { + "type": "object", + "properties": { + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerLink" + } + } + }, + "required": [ + "links" + ] + }, + "CustomerLinkRequest": { + "type": "object", + "properties": { + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerLink" + } + } + }, + "required": [ + "links" + ] + }, "ConnectionTestPingReq": { "type": "object", "properties": { @@ -8729,67 +8713,6 @@ "tags" ] }, - "EnforceMfa": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ] - }, - "CustomerLink": { - "type": "object", - "properties": { - "href": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "iconSrc": { - "type": "string" - } - }, - "required": [ - "href", - "name", - "description" - ] - }, - "CustomerLinksResponse": { - "type": "object", - "properties": { - "links": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CustomerLink" - } - } - }, - "required": [ - "links" - ] - }, - "CustomerLinkRequest": { - "type": "object", - "properties": { - "links": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CustomerLink" - } - } - }, - "required": [ - "links" - ] - }, "CustomerThemeRequest": { "type": "object", "properties": { diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 226036a..9489563 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -45,7 +45,7 @@ import { AuthSignInRes, BulkEditRequest, } from './dtos/login'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { AuthGuard } from '@nestjs/passport'; import { Request } from 'express'; import ErrorCodes, { OauthErrors } from 'src/utils/errorCodes'; diff --git a/src/modules/catalog/catalog.module.ts b/src/modules/catalog/catalog.module.ts index 45cc06e..d990344 100644 --- a/src/modules/catalog/catalog.module.ts +++ b/src/modules/catalog/catalog.module.ts @@ -2,13 +2,15 @@ import { Module } from '@nestjs/common'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; -import { CatalogController } from './catalog.controller'; +import { CatalogController } from './controllers/catalog.controller'; import { CatalogService } from './catalog.service'; import { CatalogClientConfiguration } from './catalog-client'; import { ClientsModule } from '@nestjs/microservices'; import { PipelinesModule as OldPipelineModule } from 'src/modules/pipelines/pipelines.module'; import { UsersModule } from '../users/users.module'; import { RolesModule } from '../roles/roles.module'; +import { CustomersModule } from '../customers/customers.module'; +import { CatalogPublicController } from './controllers/catalog.public.controller'; const client = new CatalogClientConfiguration(); @@ -18,8 +20,9 @@ const client = new CatalogClientConfiguration(); OldPipelineModule, UsersModule, RolesModule, + CustomersModule ], - controllers: [CatalogController], + controllers: [CatalogController, CatalogPublicController], providers: [CatalogService, DadosferaLogger], exports: [CatalogService], }) diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index a3db9cc..f7a2ed0 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -12,9 +12,11 @@ import { } from '@dadosfera/protospack-v2/dist/lib/PlatformInterfaces'; import { BadRequestException, + ForbiddenException, HttpException, HttpStatus, Inject, + NotFoundException, OnModuleInit, } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; @@ -43,6 +45,11 @@ import { CsvParser } from 'src/utils/FileParser/csv-parser'; import { PDFParser } from 'src/utils/FileParser/pdf-parser'; import { TypeParser } from 'src/utils/FileParser/parser-types'; import { ParserBuilder } from 'src/utils/FileParser/parser.builder'; +import { RequestUser } from 'src/decorators/user.decorator'; +import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; +import { CustomersService } from '../customers/customers.service'; +import { LanguageEnum } from 'src/utils/languages.enum'; class CatalogService implements OnModuleInit { catalogReadService: ReadService.CatalogReadServices; @@ -56,6 +63,7 @@ class CatalogService implements OnModuleInit { private readonly grpcClient: ClientGrpc, private readonly userService: UsersService, private readonly roleService: RolesService, + private readonly customerService: CustomersService ) { this.logger = dadosferaLogger.logger; } @@ -240,6 +248,77 @@ class CatalogService implements OnModuleInit { return { data_assets: response, total }; } + async getOneDataAssetPrivate(user: RequestUser, id: string) { + const { username, user_id, customer_id, customer_name, permissions } = user; + + this.logger.info("getOneDataAssetPrivate: " + user_id + " - " + id) + + this.logger.info(`data: `, { + username, + customer_name, + }); + + const is_data_manager = permissions.includes( + PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid, + ); + + const is_get_catalog = permissions.includes( + PERMISSIONS_GROUPS.CATALOG.permissions.GET.seqid, + ); + let has_permission = false; + const metadata = PackTheMetadata({ + username, + user_id, + customer_id, + customer_name, + }); + + const user_roles = await this.getUserRolesIds(user_id); + + const { data_asset } = await this.getOneDataAsset({ + customer_id, + id, + metadata, + }); + + has_permission = + is_data_manager || + is_get_catalog || + data_asset?.owner === username || + (user_roles as Array).some((r) => + data_asset.p_roles.includes(r), + ) || + data_asset.p_users.includes(user_id); + + + if (!has_permission) + throw new ForbiddenException( + 'You do not have permission to access this data asset.', + ); + delete data_asset.p_roles; + delete data_asset.p_users; + + return { data_asset }; + } + + async getOneDataAssetPublic(customerId: string, id: string) { + this.logger.info("getOneDataAssetPublic: " + customerId + " - " + id) + const metadata = await this.createMetadataToPublicAccess(customerId); + const { data_asset } = await this.getOneDataAsset({ + customer_id: customerId, + id, + metadata, + }); + this.logger.info("found asset: " + JSON.stringify(data_asset)) + delete data_asset.p_roles; + delete data_asset.p_users; + data_asset.share_type = 'public'; + if (data_asset.share_type !== 'public') + throw new NotFoundException(); + + return { data_asset }; + } + async getOneDataAsset(data: { id: string; customer_id: string; @@ -479,27 +558,26 @@ class CatalogService implements OnModuleInit { async createTableMetadata(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); - this.logger.info(`Nimbus URL: ${nimbusUrl}`, {...body.logMetadata}); + this.logger.info(`Nimbus URL: ${nimbusUrl}`, { ...body.logMetadata }); const endpoint = `${nimbusUrl}/api/catalog/table-metadata/`; - this.logger.info(`Creating table metadata for table ${body.table_metadata.table_name}`, {...body.logMetadata}); - this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); - this.logger.debug(`Payload: ${JSON.stringify(body.table_metadata)}`, {...body.logMetadata}); + this.logger.info(`Creating table metadata for table ${body.table_metadata.table_name}`, { ...body.logMetadata }); + this.logger.info(`Using endpoint: ${endpoint}`, { ...body.logMetadata }); + this.logger.debug(`Payload: ${JSON.stringify(body.table_metadata)}`, { ...body.logMetadata }); try { - const { data, status } = await axios.post(endpoint, {...body.table_metadata}); + const { data, status } = await axios.post(endpoint, { ...body.table_metadata }); this.logger.info( `Table metadata created successfully with status ${status} for table ${body.table_metadata.table_name}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); return data.id; } catch (error) { this.logger.error( - `Failed to create table metadata for table ${body.table_metadata.table_name} failed with status ${ - error.response?.status - } because of ${JSON.stringify(error.response?.data) || error.message}`, {...body.logMetadata}); + `Failed to create table metadata for table ${body.table_metadata.table_name} failed with status ${error.response?.status + } because of ${JSON.stringify(error.response?.data) || error.message}`, { ...body.logMetadata }); throw new Error(error.response?.data?.message || error.message); } } @@ -509,51 +587,49 @@ class CatalogService implements OnModuleInit { this.logger.info(`Nimbus URL: ${nimbusUrl}`, body.logMetadata); const endpoint = `${nimbusUrl}/api/catalog/column-metadata/`; - + try { - this.logger.info(`Creating column metadata for table ${body.column_metadata.table_name}`, {...body.logMetadata}); - this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); - this.logger.debug(`Payload: ${JSON.stringify(body.column_metadata)}`, {...body.logMetadata}); + this.logger.info(`Creating column metadata for table ${body.column_metadata.table_name}`, { ...body.logMetadata }); + this.logger.info(`Using endpoint: ${endpoint}`, { ...body.logMetadata }); + this.logger.debug(`Payload: ${JSON.stringify(body.column_metadata)}`, { ...body.logMetadata }); const { data, status } = await axios.post(endpoint, body.column_metadata); this.logger.info( `Column metadata created successfully with status ${status} for table ${body.column_metadata.table_name}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); return data.map((column) => column.id); } catch (error) { this.logger.error( - `Failed to create column metadata failed with status for table ${body.column_metadata.table_name} ${ - error.response?.status - } because of ${error.response?.data || error.message}`, {...body.logMetadata}); + `Failed to create column metadata failed with status for table ${body.column_metadata.table_name} ${error.response?.status + } because of ${error.response?.data || error.message}`, { ...body.logMetadata }); throw new Error(error.response?.data?.message || error.message); } } async createDataPreview(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); - this.logger.info(`Nimbus URL: ${nimbusUrl}`, {...body.logMetadata}); + this.logger.info(`Nimbus URL: ${nimbusUrl}`, { ...body.logMetadata }); const endpoint = `${nimbusUrl}/api/catalog/data-preview/`; - this.logger.info(`Creating data preview for table ${body.data_preview.table_name}`, {...body.logMetadata}); - this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); - this.logger.debug(`Payload: ${JSON.stringify(body.data_preview)}`, {...body.logMetadata}); + this.logger.info(`Creating data preview for table ${body.data_preview.table_name}`, { ...body.logMetadata }); + this.logger.info(`Using endpoint: ${endpoint}`, { ...body.logMetadata }); + this.logger.debug(`Payload: ${JSON.stringify(body.data_preview)}`, { ...body.logMetadata }); try { const { data, status } = await axios.post(endpoint, body.data_preview); this.logger.info( `Data preview created successfully with status ${status} for table ${body.data_preview.table_name}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); return data.id; } catch (error) { this.logger.error( - `Failed to create data preview for table ${body.data_preview.table_name} failed with status ${ - error.response?.status + `Failed to create data preview for table ${body.data_preview.table_name} failed with status ${error.response?.status } because of ${error.response?.data || error.message}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); throw new Error(error.response?.data?.message || error.message); } @@ -582,6 +658,23 @@ class CatalogService implements OnModuleInit { ); return res; } + + async createMetadataToPublicAccess(customerId?: string, language?: LanguageEnum) { + if (!customerId) { + throw new NotFoundException(); + } + + const { + customer + } = await this.customerService.getCustomer(customerId); + + return PackTheMetadata({ + customer_id: customer.id, + customer_name: customer.name, + language, + }) + } + } export { CatalogService }; diff --git a/src/modules/catalog/catalog.controller.ts b/src/modules/catalog/controllers/catalog.controller.ts similarity index 80% rename from src/modules/catalog/catalog.controller.ts rename to src/modules/catalog/controllers/catalog.controller.ts index 09daa16..ca5d88a 100644 --- a/src/modules/catalog/catalog.controller.ts +++ b/src/modules/catalog/controllers/catalog.controller.ts @@ -28,11 +28,14 @@ import { RequireAllPermissions, RequireModule, RequireSomePermission, -} from '../../decorators/authentication.decorator'; -import { DADOSFERA_MODULES_KEYS, PERMISSIONS_GROUPS } from '../../authentication/permissions.enum'; -import { CatalogService } from './catalog.service'; +} from '../../../decorators/authentication.decorator'; +import { + DADOSFERA_MODULES_KEYS, + PERMISSIONS_GROUPS, +} from '../../../authentication/permissions.enum'; +import { CatalogService } from '../catalog.service'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { RequestUser, User } from 'src/decorators/user.decorator'; import { BatchRemoveRlsRulesRequest, @@ -49,7 +52,7 @@ import { IUpdateDataRequest, TriggerCatalogReq, TriggerCatalogRes, -} from './dtos'; +} from '../dtos'; import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; import { Language } from 'src/decorators/language.decorator'; import { LanguageEnum } from 'src/utils/languages.enum'; @@ -195,209 +198,6 @@ export class CatalogController { return res; } - @Get('data-asset/:id') - @RequireSomePermission( - PERMISSIONS_GROUPS.CATALOG.permissions.GET, - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER, - ) - async getDataAsset( - @User() user: RequestUser, - @Param('id') id: string, - @Query('shared') shared?: 'true', - ) { - const { username, user_id, customer_id, customer_name, permissions } = user; - - this.logger.info(`GET /data-asset/${id}`, { - username, - customer_name, - }); - - const is_data_manager = permissions.includes( - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid, - ); - let has_permission = false; - const metadata = PackTheMetadata({ - username, - user_id, - customer_id, - customer_name, - }); - - const user_roles = await this.catalogService.getUserRolesIds(user_id); - - const { data_asset } = await this.catalogService.getOneDataAsset({ - customer_id, - id, - metadata, - }); - has_permission = - is_data_manager || - data_asset?.owner === username || - (user_roles as Array).some((r) => data_asset.p_roles.includes(r)) || - data_asset.p_users.includes(user_id); - if ( - shared === 'true' && - (data_asset.share_type === undefined || data_asset.share_type === 'none') - ) - throw new NotFoundException(); - if (!has_permission) - throw new ForbiddenException( - 'You do not have permission to access this data asset.', - ); - delete data_asset.p_roles; - delete data_asset.p_users; - - return { data_asset }; - } - - @ApiInternalOnlyEndpoint() - @Get('data-asset/rls/:id') - @RequireSomePermission( - PERMISSIONS_GROUPS.CATALOG.permissions.GET, - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER, - ) - async getDataAssetRls( - @User() user: RequestUser, - @Headers() headers, - @Param('id') id: string, - ) { - const { username, user_id, customer_id, customer_name, permissions } = user; - - this.logger.info(`/catalog - ON GET ONE DASHBOARD METABASE ROUTE`, { - username, - customer_name, - }); - - const is_data_manager = permissions.includes( - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid, - ); - let has_permission = false; - const metadata = PackTheMetadata({ - username, - user_id, - customer_id, - customer_name, - }); - - const user_roles = await this.catalogService.getUserRolesIds(user_id); - - const { data_asset } = await this.catalogService.getOneDataAsset({ - customer_id, - id, - metadata, - }); - - if (data_asset?.owner === username) has_permission = true; - - for (const role of user_roles) { - if (data_asset.p_roles.includes(role)) has_permission = true; - } - - if (data_asset.p_users.includes(user_id)) has_permission = true; - - if (is_data_manager || has_permission) { - delete data_asset.p_roles; - delete data_asset.p_users; - return { data_asset }; - } - - throw new ForbiddenException( - 'You do not have permission to access this data asset.', - ); - } - - @Get('data-asset/:id/columns-metadata') - @RequireSomePermission( - PERMISSIONS_GROUPS.CATALOG.permissions.GET, - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER, - ) - async getDataAssetColumnsMetadata( - @User() user: RequestUser, - @Language() language: LanguageEnum, - @Param('id') id: string, - ): Promise { - const { customer_name, customer_id, user_id, username } = user; - - this.logger.info(`/catalog - columns-metadata`, { - user_id, - customer_name, - }); - - const metadata = PackTheMetadata({ - customer_name, - customer_id, - user_id, - username, - language, - }); - - const columns_metadata = - await this.catalogService.getDatasetColumnsMetadata(id, metadata); - - return { columns_metadata }; - } - - @Get('data-asset/:id/preview') - @RequireSomePermission( - PERMISSIONS_GROUPS.CATALOG.permissions.GET, - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER, - ) - async getDataAssetPreview( - @User() user: RequestUser, - @Language() language: LanguageEnum, - @Param('id') id: string, - ): Promise { - const { customer_name, customer_id, user_id, username, customer_modules } = user; - - this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, { - user_id, - customer_name, - }); - - const metadata = PackTheMetadata({ - customer_name, - customer_id, - user_id, - username, - language, - is_mask: customer_modules.some(mod => mod === 'pii') - }); - - const preview = await this.catalogService.getDatasetPreview(id, metadata); - - return { preview }; - } - - @Get('data-asset/:id/docs') - @RequireSomePermission( - PERMISSIONS_GROUPS.CATALOG.permissions.GET, - PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER, - ) - async getDataAssetDocs( - @User() user: RequestUser, - @Language() language: LanguageEnum, - @Param('id') id: string, - ): Promise { - const { customer_name, customer_id, user_id, username } = user; - - this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, { - user_id, - customer_name, - }); - - const metadata = PackTheMetadata({ - customer_name, - customer_id, - user_id, - username, - language, - }); - - const docs = await this.catalogService.getDataDocs(id, metadata); - - return { docs }; - } - @Put('data-asset/:id') @RequireSomePermission( PERMISSIONS_GROUPS.CATALOG.permissions.UPDATE, @@ -648,6 +448,62 @@ export class CatalogController { return response; } + @ApiInternalOnlyEndpoint() + @Get('data-asset/rls/:id') + @RequireSomePermission( + PERMISSIONS_GROUPS.CATALOG.permissions.GET, + PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER, + ) + async getDataAssetRls( + @User() user: RequestUser, + @Headers() headers, + @Param('id') id: string, + ) { + const { username, user_id, customer_id, customer_name, permissions } = user; + + this.logger.info(`/catalog - ON GET ONE DASHBOARD METABASE ROUTE`, { + username, + customer_name, + }); + + const is_data_manager = permissions.includes( + PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid, + ); + let has_permission = false; + const metadata = PackTheMetadata({ + username, + user_id, + customer_id, + customer_name, + }); + + const user_roles = await this.catalogService.getUserRolesIds(user_id); + + const { data_asset } = await this.catalogService.getOneDataAsset({ + customer_id, + id, + metadata, + }); + + if (data_asset?.owner === username) has_permission = true; + + for (const role of user_roles) { + if (data_asset.p_roles.includes(role)) has_permission = true; + } + + if (data_asset.p_users.includes(user_id)) has_permission = true; + + if (is_data_manager || has_permission) { + delete data_asset.p_roles; + delete data_asset.p_users; + return { data_asset }; + } + + throw new ForbiddenException( + 'You do not have permission to access this data asset.', + ); + } + @Post('rls-rule') @RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER) async addRlsRule(@User() user: RequestUser, @Body() body: AddRlsRuleRequest) { @@ -859,29 +715,31 @@ export class CatalogController { } @Get('pii-reporter') - @RequireSomePermission( - PERMISSIONS_GROUPS.USERS.permissions.ADMIN - ) - @RequireModule( - DADOSFERA_MODULES_KEYS.PII - ) - async getPiiReporter(@User() user: RequestUser, @Res() res: Response, @Query('type') contentType: TypeParser = "pdf") { + @RequireSomePermission(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) + @RequireModule(DADOSFERA_MODULES_KEYS.PII) + async getPiiReporter( + @User() user: RequestUser, + @Res() res: Response, + @Query('type') contentType: TypeParser = 'pdf', + ) { this.logger.info('GET pii-reporter'); const metadata = PackTheMetadata(user); try { - const { - file, - filename, - type - } = await this.catalogService.getPiiReporter(metadata, contentType); - res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + const { file, filename, type } = await this.catalogService.getPiiReporter( + metadata, + contentType, + ); + res.setHeader( + 'Content-Disposition', + `attachment; filename="${filename}"`, + ); res.setHeader('Content-Type', type); // use res.end to send buffer return res.end(file); } catch (error) { - console.error(error) + console.error(error); this.logger.error(error.message); } } diff --git a/src/modules/catalog/controllers/catalog.public.controller.ts b/src/modules/catalog/controllers/catalog.public.controller.ts new file mode 100644 index 0000000..6d3be81 --- /dev/null +++ b/src/modules/catalog/controllers/catalog.public.controller.ts @@ -0,0 +1,184 @@ +import { + Controller, + ForbiddenException, + Get, + Headers, + Inject, + Param, + Query, + UseFilters, +} from '@nestjs/common'; +import { + ApiHeaders, + ApiTags, +} from '@nestjs/swagger'; +import { + RequireSomePermission, +} from '../../../decorators/authentication.decorator'; +import { + PERMISSIONS_GROUPS, +} from '../../../authentication/permissions.enum'; +import { CatalogService } from '../catalog.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; +import { RequestUser, User } from 'src/decorators/user.decorator'; +import { + IColumnsMetadataResponse, + IDocsResponse, + IPreviewResponse, + +} from '../dtos'; +import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; +import { Language } from 'src/decorators/language.decorator'; +import { LanguageEnum } from 'src/utils/languages.enum'; +import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; + + +@ApiTags('Catalog') +@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }]) +@Controller('catalog') +@UseFilters(new GrpcToHttpExceptionFilter()) +export class CatalogPublicController { + logger: DadosferaLogger; + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private catalogService: CatalogService, + ) { + this.logger = dadosferaLogger.logger; + } + + private async createAccessToPublicAssets( + user?: RequestUser, + customerId?: string, + language?: LanguageEnum, + ) { + if (user) { + const { customer_name, customer_id, user_id, username } = user; + return PackTheMetadata({ + customer_name, + customer_id, + user_id, + username, + language, + }); + } else { + return await this.catalogService.createMetadataToPublicAccess( + customerId, + language, + ); + } + } + + private checkDefaultPermission(user: RequestUser) { + const is_data_manager = user.permissions.includes( + PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid, + ); + + const is_get = user.permissions.includes( + PERMISSIONS_GROUPS.CATALOG.permissions.GET.seqid, + ); + + if (is_data_manager || is_get) { + return; + } + + throw new ForbiddenException( + 'You do not have permission to access this data asset.', + ); + } + + @Get('data-asset/:id') + async getDataAsset( + @User() user: RequestUser, + @Param('id') id: string, + @Query('c') customerId?: string, + ) { + this.logger.info(`GET /data-asset/${id}`); + try { + if (user) { + return await this.catalogService.getOneDataAssetPrivate(user, id); + } else { + return await this.catalogService.getOneDataAssetPublic(customerId, id); + } + } catch (error) { + this.logger.error(error.message); + throw error; + } + } + + + + @Get('data-asset/:id/columns-metadata') + async getDataAssetColumnsMetadata( + @User() user: RequestUser, + @Language() language: LanguageEnum, + @Param('id') id: string, + @Query('c') customerId: string, + ): Promise { + const metadata = await this.createAccessToPublicAssets( + user, + customerId, + language, + ); + this.logger.info(`/catalog - columns-metadata`); + + const columns_metadata = + await this.catalogService.getDatasetColumnsMetadata(id, metadata); + + if (user) { + this.checkDefaultPermission(user); + } + + return { columns_metadata }; + } + + @Get('data-asset/:id/preview') + async getDataAssetPreview( + @User() user: RequestUser, + @Language() language: LanguageEnum, + @Param('id') id: string, + @Query('c') customerId: string, + ): Promise { + this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`); + + const metadata = await this.createAccessToPublicAssets( + user, + customerId, + language, + ); + + const preview = await this.catalogService.getDatasetPreview(id, metadata); + + if (user) { + this.checkDefaultPermission(user); + } + + return { preview }; + } + + @Get('data-asset/:id/docs') + async getDataAssetDocs( + @User() user: RequestUser, + @Language() language: LanguageEnum, + @Param('id') id: string, + @Query('c') customerId: string, + ): Promise { + this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`); + + const metadata = await this.createAccessToPublicAssets( + user, + customerId, + language, + ); + + const docs = await this.catalogService.getDataDocs(id, metadata); + + if (user) { + this.checkDefaultPermission(user); + } + + return { docs }; + } + +} diff --git a/src/modules/connection-test/connection-test.service.ts b/src/modules/connection-test/connection-test.service.ts index 310555a..3b92e12 100644 --- a/src/modules/connection-test/connection-test.service.ts +++ b/src/modules/connection-test/connection-test.service.ts @@ -20,7 +20,7 @@ import { DatabaseConnectionPropertiesDto, } from '../connection/dtos/connection'; import { RequestUser } from 'src/decorators/user.decorator'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; @Injectable() export class ConnectionTestService { diff --git a/src/modules/connection/connection.controller.ts b/src/modules/connection/connection.controller.ts index 7c4275a..194e9aa 100644 --- a/src/modules/connection/connection.controller.ts +++ b/src/modules/connection/connection.controller.ts @@ -28,7 +28,7 @@ import { UpdateConnectionDto, } from './dtos/connection'; import { CreateConnectionDto } from './dtos/connection'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; import { Language } from 'src/decorators/language.decorator'; import { LanguageEnum } from 'src/utils/languages.enum'; diff --git a/src/modules/customers/customers.controller.ts b/src/modules/customers/customers.controller.ts index bcab557..2606ebd 100644 --- a/src/modules/customers/customers.controller.ts +++ b/src/modules/customers/customers.controller.ts @@ -25,7 +25,7 @@ import { CustomersService } from './customers.service'; import { CustomerLinkRequest, CustomerLinksResponse } from './dtos/customers'; import { RequestUser, User } from 'src/decorators/user.decorator'; import type { StringValue } from 'ms'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { EnforceMfa } from './dtos/enforce-mfa'; @ApiTags('Customers') diff --git a/src/modules/customers/customers.service.ts b/src/modules/customers/customers.service.ts index 056c5fb..8b28171 100644 --- a/src/modules/customers/customers.service.ts +++ b/src/modules/customers/customers.service.ts @@ -59,6 +59,14 @@ export class CustomersService implements OnModuleInit { ); } + async getCustomer(customerId: string) { + return await lastValueFrom( + this.customerService.CustomerFindOneById({ + id: customerId + }) + ) + } + async getLinks(customerId: string) { try { const result = await lastValueFrom( @@ -141,6 +149,7 @@ export class CustomersService implements OnModuleInit { // const decoded = jwt.decode(jwt_token, { complete: true }); return jwt_token; } + async getMonitoringDashboardUrl(metadata: Metadata) { logger.info('CustomersService - getMonitoringDashboardUrl'); @@ -158,6 +167,7 @@ export class CustomersService implements OnModuleInit { return res; } + async getLogsDashboardUrl(metadata: Metadata) { logger.info('CustomersService - getMixPanelLogsDashboardUrl'); diff --git a/src/modules/network-policy/network-policy.controller.ts b/src/modules/network-policy/network-policy.controller.ts index 8ae8371..f45ffdd 100644 --- a/src/modules/network-policy/network-policy.controller.ts +++ b/src/modules/network-policy/network-policy.controller.ts @@ -8,7 +8,7 @@ import { import { NetworkPoliciesDTO } from './dto/network-policy.dto'; import { RequestUser, User } from 'src/decorators/user.decorator'; import { NetworkPolicyService } from './network-policy.service'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; @Controller('network-policy') export class NetworkPolicyController { diff --git a/src/modules/oauth/oauth.controller.ts b/src/modules/oauth/oauth.controller.ts index 1e87866..e1af4c9 100644 --- a/src/modules/oauth/oauth.controller.ts +++ b/src/modules/oauth/oauth.controller.ts @@ -4,7 +4,7 @@ import { AuthGuard } from '@nestjs/passport'; import { ConnectionClientService } from '../connection/client.service'; import jwt from 'jsonwebtoken'; import DadosferaLogger from '@dadosfera/dadosfera-logs/dist'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; @ApiTags('oauth') @Controller('oauth') diff --git a/src/modules/open-data/open-data.controller.ts b/src/modules/open-data/open-data.controller.ts index b8951ed..a2fa72a 100644 --- a/src/modules/open-data/open-data.controller.ts +++ b/src/modules/open-data/open-data.controller.ts @@ -9,7 +9,7 @@ import { Language } from 'src/decorators/language.decorator'; import { OpenDataService } from './open-data.service'; import { CreateUserOpenDataDTO, WordpressForm } from './dto/wordpres-form'; import { Metadata } from '@grpc/grpc-js'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { request } from 'http'; import { Request } from 'express'; diff --git a/src/modules/pipelinesV2/pipelines.controller.ts b/src/modules/pipelinesV2/pipelines.controller.ts index 512a705..6986f25 100644 --- a/src/modules/pipelinesV2/pipelines.controller.ts +++ b/src/modules/pipelinesV2/pipelines.controller.ts @@ -32,7 +32,7 @@ import { PipelinesService } from './pipelines.service'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { Messages } from '@dadosfera/protospack-v2/dist/lib/PipelineV2'; import { RequestUser, User } from 'src/decorators/user.decorator'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { PipelinesService as OldPipelineService } from 'src/modules/pipelines/pipelines.service'; import { diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 9e00f7c..5fdc603 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -29,7 +29,7 @@ import { Language } from 'src/decorators/language.decorator'; import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator'; import { RequestUser, User } from 'src/decorators/user.decorator'; import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; -import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import ErrorBuilder from 'src/utils/ErrorBuilder'; import ErrorCodes from 'src/utils/errorCodes'; import { LanguageEnum } from 'src/utils/languages.enum'; diff --git a/src/utils/ PackTheMetadata.ts b/src/utils/PackTheMetadata.ts similarity index 100% rename from src/utils/ PackTheMetadata.ts rename to src/utils/PackTheMetadata.ts