import DadosferaLogger from '@dadosfera/dadosfera-logs'; import { ProtoServices, ReadService, } from '@dadosfera/protospack-v2/dist/lib/Catalog'; import { ForbiddenException, Inject, NotFoundException, OnModuleInit, } from '@nestjs/common'; import { CatalogClientConfiguration } from '../catalog-client'; import { ClientGrpc } from '@nestjs/microservices'; import { UsersService } from 'src/modules/users/users.service'; import { RolesService } from 'src/modules/roles/roles.service'; import { RequestUser } from 'src/decorators/user.decorator'; import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; import { Metadata } from '@grpc/grpc-js'; import { lastValueFrom } from 'rxjs'; import { ShareMetadataService } from 'src/modules/share-metadata/share-metadata.service'; import { isJWT } from 'class-validator'; import { MixpanelService } from 'src/modules/mixpanel/mixpanel.service'; import { Request } from 'express'; import jwt from 'jsonwebtoken'; import { AuthClientService } from 'src/modules/auth/auth.service'; export class ShareService implements OnModuleInit { catalogReadService: ReadService.CatalogReadServices; logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, @Inject(CatalogClientConfiguration.name) private readonly grpcClient: ClientGrpc, private readonly userService: UsersService, private readonly roleService: RolesService, private readonly shareMetadataService: ShareMetadataService, private readonly mixpanelService: MixpanelService, private authClient: AuthClientService, ) { this.logger = dadosferaLogger.logger; } onModuleInit() { this.catalogReadService = this.grpcClient.getService( ProtoServices.CatalogReadServices, ); } async getDatasetColumnsMetadata(id: string, request: Request) { const shareMetadata = await this.getShareMetadata(id, request); const metadata = PackTheMetadata({ customer_id: shareMetadata.customerId, customer_name: shareMetadata.customerName, }); const { columns_metadata } = await lastValueFrom( this.catalogReadService.GetDatasetColumnsMetadata( { id: shareMetadata.assetId, type: undefined }, metadata, ), ); const result = JSON.parse(columns_metadata); return result; } async getDatasetPreview(id: string, request: Request) { const shareMetadata = await this.getShareMetadata(id, request); const metadata = PackTheMetadata({ customer_id: shareMetadata.customerId, customer_name: shareMetadata.customerName, }); const { preview } = await lastValueFrom( this.catalogReadService.GetDatasetPreview( { id: shareMetadata.assetId, type: undefined }, metadata, ), ); const result = JSON.parse(preview); return result; } async getOneDataAssetPublic(id: string, request: Request) { this.logger.info("getOneDataAssetPublic: " + JSON.stringify({ id })) try { const user = await this.getUserFromRequest(request); const shareMetadata = await this.getShareMetadata(id, request); const mixpanelTracker = { asset: shareMetadata.assetId, type: isJWT(id) ? 'assigned' : shareMetadata.type, customer: shareMetadata.customerName } if (user) { await this.mixpanelService.track("share_page", user, request, mixpanelTracker); } else { await this.mixpanelService.trackShare(request, mixpanelTracker); } this.logger.info("shareMetadata: " + JSON.stringify(shareMetadata)) const metadata = PackTheMetadata({ customer_id: shareMetadata.customerId, customer_name: shareMetadata.customerName, }); const { data_asset } = await this.getOneDataAsset({ customer_id: shareMetadata.customerId, id: shareMetadata.assetId, 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 }; } catch (error) { this.logger.error(error); throw error; } } private async getOneDataAsset(data: { id: string; customer_id: string; metadata: Metadata; }) { const { customer_id, id, metadata } = data; const { data_asset } = await lastValueFrom( this.catalogReadService.GetOneDataAsset( { id, type: undefined }, metadata, ), ); let asset = JSON.parse(data_asset); asset = { ...asset, p_roles: asset.roles, p_users: asset.users, }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); return { data_asset: asset[0] }; } async getDataDocs(id: string, request: Request) { const shareMetadata = await this.getShareMetadata(id, request); const metadata = PackTheMetadata({ customer_id: shareMetadata.customerId, customer_name: shareMetadata.customerName, }); const { documentation } = await lastValueFrom( this.catalogReadService.GetDatasetDoc({ id }, metadata), ); console.log(documentation); const docs = JSON.parse(documentation); return docs; } private async getAssetsUsersAndRoles( data_assets: Array, customer_id: string, ) { const { users: customer_users } = await this.userService.findAllUsersByCustomerId(customer_id); const { roles: customer_roles } = await this.roleService.roleSearch( {}, { customer_id }, ); return data_assets.map((data_asset) => { const owner = customer_users.find( (u) => u.id === data_asset.owner, )?.email; const roles = []; const users = []; for (const role_id of data_asset.roles) { const role = customer_roles.find((r) => r.id === role_id); if (role) roles.push({ id: role.id, name: role.name }); } for (const user_id of data_asset.users) { const user = customer_users.find((r) => r.id === user_id); if (user) users.push({ id: user.id, email: user.email }); } return { ...data_asset, roles, users, owner, } as typeof data_asset; }); } private async getShareMetadata(id: string, request: Request) { const metadata = PackTheMetadata({}); this.logger.info('GET share metadata') const info = await this.shareMetadataService.get(id, metadata); if (isJWT(id) && info ){ return info; } const user = await this.getUserFromRequest(request); if (info.type === 'private') { if (!user) { throw new ForbiddenException( 'You do not have permission to access this data asset.', ); } 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 info; } throw new ForbiddenException( 'You do not have permission to access this data asset.', ); } return info; } private async getUserFromRequest(request: Request): Promise { const accessToken = request.get('Authorization'); if (accessToken) { const accessTokenDecoded: any = jwt.decode(accessToken, { complete: true, }); const { kid } = accessTokenDecoded.header; const { keys } = await this.authClient.getPublicKeys(); const pemValue = keys.find((key) => key.kid === kid); if (!pemValue) { return null; } jwt.verify(accessToken, pemValue.pem); const accessTokenPayload = accessTokenDecoded.payload; return { user_id: accessTokenPayload.user_id, username: accessTokenPayload.username, permissions: accessTokenPayload.permissions, customer_id: accessTokenPayload.customer_id, customer_name: accessTokenPayload.customer_name, customer_tier: accessTokenPayload.customer_tier, customer_modules: accessTokenPayload.customer_modules, access_token: accessToken, }; } return null; } }