diff --git a/README.md b/README.md index ccb4f5b..7693260 100644 --- a/README.md +++ b/README.md @@ -299,3 +299,4 @@ Some technologies used in this project: ## ⚙️ Back-end Architecture The architecture can be found at [this link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end). + diff --git a/docsfera.json b/docsfera.json index 7bc5522..7c3eb1f 100644 --- a/docsfera.json +++ b/docsfera.json @@ -2919,7 +2919,16 @@ "/catalog/data-asset/{id}": { "get": { "operationId": "CatalogController_getDataAsset", - "parameters": [], + "parameters": [ + { + "name": "shared", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "" @@ -5976,6 +5985,14 @@ }, "embed": { "$ref": "#/components/schemas/EmbedObject" + }, + "share_type": { + "type": "string", + "enum": [ + "none", + "public", + "private" + ] } }, "required": [ diff --git a/src/authentication/permissions.enum.ts b/src/authentication/permissions.enum.ts index aa94d20..a252deb 100644 --- a/src/authentication/permissions.enum.ts +++ b/src/authentication/permissions.enum.ts @@ -336,6 +336,16 @@ export const PERMISSIONS_GROUPS = { 'es-es': 'Gestor de catálogos. Puede ver y editar todos los activos.', }, }, + EMBED_ANALYTICS: { + seqid: 44, + claim: 'catalog:embed', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Acessar Módulo de Incorporação de Ativos', + 'en-us': 'Access Embedding analytics Module', + 'es-es': 'Acceder al Módulo de Incorporación de Activos', + }, + }, }, }, @@ -556,7 +566,34 @@ export const PERMISSIONS_GROUPS = { }, }, }; - +export interface DadosferaModule { + name: string; + description: string; + key: string; + permissionSeqId: number; +} +export const DADOSFERA_MODULES: Array = [ + { + name: 'Intelligence Module', + description: 'Orchest Module', + key: 'intelligence', + permissionSeqId: PERMISSIONS_GROUPS.ANALYZE.permissions.INTELLIGENCE.seqid, + }, + { + name: 'Proccessing Module', + description: 'Proccessing Module', + key: 'process', + permissionSeqId: + PERMISSIONS_GROUPS.PROCESS.permissions.TRANSFORMATION.seqid, + }, + { + name: 'Embedded Analytics', + description: 'Embedded Analytics Module', + key: 'embedded-analytics', + permissionSeqId: + PERMISSIONS_GROUPS.PROCESS.permissions.TRANSFORMATION.seqid, + }, +]; // traverses the object searching for duplicate seqids or claims (executes at runtime) let nextAvailableSeqid = 0; const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) => @@ -576,4 +613,9 @@ Object.values(PERMISSIONS_GROUPS).map((namespace) => }), ); +DADOSFERA_MODULES.map((m) => m.key).forEach((m, i, arr) => { + if (arr.indexOf(m) !== i) + throw new Error(`DADOSFERA_MODULES[${i}] does not have a unique key`); +}); + logger.log(`next available seqid ${nextAvailableSeqid + 1}`); diff --git a/src/modules/catalog/catalog.controller.ts b/src/modules/catalog/catalog.controller.ts index dcbe404..71f69f3 100644 --- a/src/modules/catalog/catalog.controller.ts +++ b/src/modules/catalog/catalog.controller.ts @@ -1,11 +1,14 @@ import { + BadRequestException, Body, Controller, Delete, + ForbiddenException, Get, Headers, HttpException, Inject, + NotFoundException, Param, Post, Put, @@ -98,7 +101,7 @@ export class CatalogController { }); if (!pipeline || !object) { - throw new HttpException('Query params not provided', 400); + throw new BadRequestException('Query params not provided'); } const is_data_manager = permissions.includes( @@ -136,9 +139,8 @@ export class CatalogController { return { data_asset }; } - throw new HttpException( + throw new ForbiddenException( 'You do not have permission to access this data asset.', - 403, ); } @@ -172,12 +174,12 @@ export class CatalogController { ) async getDataAsset( @User() user: RequestUser, - @Headers() headers, @Param('id') id, + @Query('shared') shared?: 'true', ) { const { username, user_id, customer_id, customer_name, permissions } = user; - this.logger.info(`/catalog - ON GET ONE DASHBOARD METABASE ROUTE`, { + this.logger.info(`GET /data-asset/${id}`, { username, customer_name, }); @@ -200,25 +202,24 @@ export class CatalogController { 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; - 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 HttpException( - 'You do not have permission to access this data asset.', - 403, - ); + return { data_asset }; } @Get('data-asset/rls/:id') @@ -271,9 +272,8 @@ export class CatalogController { return { data_asset }; } - throw new HttpException( + throw new ForbiddenException( 'You do not have permission to access this data asset.', - 403, ); } @@ -376,7 +376,7 @@ export class CatalogController { async updateDataAsset( @User() user: RequestUser, @Headers('Dadosfera-Lang') language, - @Param('id') id, + @Param('id') data_asset_id, @Body() body: IUpdateDataRequest, ): Promise { const { customer_id, customer_name, user_id, username } = user; @@ -390,7 +390,7 @@ export class CatalogController { const result = await this.catalogService.updateOneDataAsset({ body, - data_asset_id: id, + data_asset_id, customer_id, metadata, }); diff --git a/src/modules/catalog/dtos/index.ts b/src/modules/catalog/dtos/index.ts index 16604b2..e55872f 100644 --- a/src/modules/catalog/dtos/index.ts +++ b/src/modules/catalog/dtos/index.ts @@ -1,6 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { CreateDataAssetRequest } from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages'; +export enum DataAssetShareType { + none = 'none', + public = 'public', + private = 'private', +} export class EmbedObject { @ApiProperty() url: string; @@ -135,6 +140,8 @@ export class IUpdateDataRequest { tags: string[]; @ApiPropertyOptional() embed: EmbedObject; + @ApiPropertyOptional({ enum: DataAssetShareType }) + share_type: DataAssetShareType; } export class ICreateDataAsset implements CreateDataAssetRequest { @ApiProperty() diff --git a/src/modules/duc/client.config.ts b/src/modules/duc/client.config.ts index 3b0ecc7..1bcd08e 100644 --- a/src/modules/duc/client.config.ts +++ b/src/modules/duc/client.config.ts @@ -9,6 +9,8 @@ import { ProtoPaths, } from '@dadosfera/protospack-v2/dist/lib/Duc'; +const isLocalConnection = !!process.env.DUC_URL?.includes('0.0.0.0'); + export class DucClient { public name = 'DucClient'; @@ -17,10 +19,7 @@ export class DucClient { options: { url: process.env.DUC_URL, package: [ProtoPackages.WritePackage, ProtoPackages.ReadPackage], - credentials: - process.env.LOCAL_ENV || process.env.ENV === 'local' - ? undefined - : credentials.createSsl(), + credentials: isLocalConnection ? undefined : credentials.createSsl(), protoPath: [ProtoPaths.WriteFilePath, ProtoPaths.ReadFilePath], loader: { keepCase: true, diff --git a/src/modules/inputs/inputs-client.config.ts b/src/modules/inputs/inputs-client.config.ts index 7834231..b52009f 100644 --- a/src/modules/inputs/inputs-client.config.ts +++ b/src/modules/inputs/inputs-client.config.ts @@ -2,10 +2,11 @@ import { Input } from '@dadosfera/protospack-v2'; import { credentials } from '@grpc/grpc-js'; import { ClientProviderOptions, - GrpcOptions, Transport, + type GrpcOptions, } from '@nestjs/microservices'; +const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0'); export class InputsGrpcClient { public readonly name = 'InputsGrpcClient'; private config: GrpcOptions = { @@ -16,10 +17,7 @@ export class InputsGrpcClient { Input.ProtoPackages.WritePackage, Input.ProtoPackages.ReadPackage, ], - credentials: - process.env.LOCAL_ENV || process.env.ENV === 'local' - ? undefined - : credentials.createSsl(), + credentials: isLocalConnection ? undefined : credentials.createSsl(), protoPath: [ Input.ProtoPaths.WriteFilePath, Input.ProtoPaths.ReadFilePath, diff --git a/src/modules/mixpanel/mixpanel.module.ts b/src/modules/mixpanel/mixpanel.module.ts index 41b4a68..98f130d 100644 --- a/src/modules/mixpanel/mixpanel.module.ts +++ b/src/modules/mixpanel/mixpanel.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { getSecreteFromSecreteManager } from 'src/utils/SecretManager'; +import { getSecretFromSecretsManager } from 'src/utils/SecretManager'; import { MixpanelController } from './mixpanel.controller'; @Module({ @@ -7,7 +7,7 @@ import { MixpanelController } from './mixpanel.controller'; providers: [ { provide: 'MIXPANEL_TOKEN', - useValue: getSecreteFromSecreteManager( + useValue: getSecretFromSecretsManager( `${process.env.ENV}/root/mixpanel_token`, ), }, diff --git a/src/modules/oauth/passport-strategies/facebook-strategy.ts b/src/modules/oauth/passport-strategies/facebook-strategy.ts index 289e842..1384d76 100644 --- a/src/modules/oauth/passport-strategies/facebook-strategy.ts +++ b/src/modules/oauth/passport-strategies/facebook-strategy.ts @@ -44,7 +44,8 @@ export class FacebookStrategy extends PassportStrategy(Strategy) { req, 'application', ); - options.scope = 'ads_read ads_management'; + options.scope = + 'pages_show_list ads_read pages_read_engagement ads_management'; super.authenticate(req, options); } } diff --git a/src/modules/productboard/productboard.controller.ts b/src/modules/productboard/productboard.controller.ts index c28bf7f..55327df 100644 --- a/src/modules/productboard/productboard.controller.ts +++ b/src/modules/productboard/productboard.controller.ts @@ -2,7 +2,7 @@ import { Body, Controller, HttpException, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import axios from 'axios'; import { User, RequestUser } from 'src/authentication/user.decorator'; -import { getSecreteFromSecreteManager } from 'src/utils/SecretManager'; +import { getSecretFromSecretsManager } from 'src/utils/SecretManager'; import { INote } from './dtos'; @ApiTags('Productboard') @@ -18,7 +18,7 @@ export class ProductboardController { : username + `@${customer_name}.default`; const path = process.env.PB_TOKEN_PATH; - const token = await getSecreteFromSecreteManager(path); + const token = await getSecretFromSecretsManager(path); const response = await axios .post( diff --git a/src/utils/SecretManager.ts b/src/utils/SecretManager.ts index 08419bd..98fcc7c 100644 --- a/src/utils/SecretManager.ts +++ b/src/utils/SecretManager.ts @@ -3,7 +3,7 @@ import { GetSecretValueCommand, } from '@aws-sdk/client-secrets-manager'; -export async function getSecreteFromSecreteManager(path: string) { +export async function getSecretFromSecretsManager(path: string) { const secretsManagerClient = new SecretsManagerClient({}); const getSecretComand = new GetSecretValueCommand({