diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index 7c46aa8416..731e66e1d3 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -3,6 +3,14 @@ on: [push] jobs: buildAndTest: runs-on: ubuntu-latest + services: + hashicorpvault: + image: vault + env: + VAULT_SERVER: "http://0.0.0.0:8200" + VAULT_DEV_ROOT_TOKEN_ID: "1234" + ports: + - 8200/tcp strategy: matrix: node-version: [16.x] @@ -68,6 +76,8 @@ jobs: OPERATOR_ID: ${{ secrets.OPERATOR_ID }} OPERATOR_KEY: ${{ secrets.OPERATOR_KEY }} IPFS_STORAGE_API_KEY: ${{ secrets.IPFS_STORAGE_API_KEY }} + HASHICORP_HOST: localhost + HASHICORP_PORT: ${{ job.services.hashicorpvault.ports[8200] }} - name: Publish API Test Results uses: EnricoMi/publish-unit-test-result-action@v1 if: always() diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88386f4437..0377b57425 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,6 +69,12 @@ jobs: pushd guardian-service npm run lint popd + pushd worker-service + npm run lint + popd + pushd ipfs-client + npm run lint + popd pushd api-gateway npm run lint popd @@ -79,6 +85,9 @@ jobs: pushd guardian-service npm run test popd + pushd worker-service + npm run test + popd env: CI: true OPERATOR_ID: ${{ secrets.OPERATOR_ID }} diff --git a/Demo Artifacts/Remote Work/Remote Work GHG Policy.policy b/Demo Artifacts/Remote Work/Remote Work GHG Policy.policy new file mode 100644 index 0000000000..6d4c3b4113 Binary files /dev/null and b/Demo Artifacts/Remote Work/Remote Work GHG Policy.policy differ diff --git a/Demo Artifacts/Tolam Earth/readme.md b/Demo Artifacts/Tolam Earth/readme.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Demo Artifacts/Tolam Earth/readme.md @@ -0,0 +1 @@ + diff --git a/Demo Artifacts/Verra/Verra Redd/VM0007/Policies/Verra REDD Policy 3 groups (1663846582.307635866).policy b/Demo Artifacts/Verra/Verra Redd/VM0007/Policies/Verra REDD Policy 3 groups (1663846582.307635866).policy new file mode 100644 index 0000000000..11c05e08cb Binary files /dev/null and b/Demo Artifacts/Verra/Verra Redd/VM0007/Policies/Verra REDD Policy 3 groups (1663846582.307635866).policy differ diff --git a/Demo Artifacts/iREC/Policies/IRec Policy 5 group (1663850151.496004277).policy b/Demo Artifacts/iREC/Policies/IRec Policy 5 group (1663850151.496004277).policy new file mode 100644 index 0000000000..7ce27d1c88 Binary files /dev/null and b/Demo Artifacts/iREC/Policies/IRec Policy 5 group (1663850151.496004277).policy differ diff --git a/Demo Artifacts/iREC/readme.md b/Demo Artifacts/iREC/readme.md index bded92d640..90509e33ab 100644 --- a/Demo Artifacts/iREC/readme.md +++ b/Demo Artifacts/iREC/readme.md @@ -21,9 +21,9 @@ As the initial step in IREC Policy involve creation of following Schemas: iRec r | Inverter Schema | 1644847093.979895804 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Schema/Inverter.schema) | | MRV Schema | 1644847107.415192828 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Schema/MRV.schema) | | iREC 1 | 1661166202.802071003 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/iRec%20Policy.policy) | -| iREC 2 | 1661167347.064745885 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/iRec%20Policy%202.policy) | -| iREC 3 | 1661372984.332898003 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/IRec%20Policy%203.policy) | -| iREC 4 | 1661373147.975461003 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/IRec%20Policy%204.policy) | +| iREC 2 | 1662640724.951854568 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/iRec%20Policy%202.policy) | +| iREC 3 | 1662641840.731000003 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/IRec%20Policy%203.policy) | +| iREC 4 | 1662642008.325450377 | [Link](https://github.com/hashgraph/guardian/blob/main/Demo%20Artifacts/iREC/Policies/IRec%20Policy%204.policy) | For complete User Guide and API Flow for executing IREC Policy, please refer to : diff --git a/README.md b/README.md index e356ba7f1a..53f61a6e04 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,17 @@ If you build with docker [MongoDB](https://www.mongodb.com), [NodeJS](https://no ```plaintext IPFS_STORAGE_API_KEY="" ``` + in `worker-service/.env`: + ```plaintext + IPFS_STORAGE_API_KEY="" + ``` + in `worker-service/.env.docker`: + + ```plaintext + IPFS_STORAGE_API_KEY="" + ``` + 4. Build and launch with Docker. Please note that this build is meant to be used in production and will not contain any debug information. From the project's root folder: ```shell diff --git a/api-docs/api/swagger/swagger.yaml b/api-docs/api/swagger/swagger.yaml index 4de25e96b6..936452d58c 100644 --- a/api-docs/api/swagger/swagger.yaml +++ b/api-docs/api/swagger/swagger.yaml @@ -2,7 +2,7 @@ openapi: 3.0.0 info: title: "Guardian" description: "The Guardian is a modular open-source solution that includes best-in-class identity management and decentralized ledger technology (DLT) libraries. At the heart of the Guardian solution is a sophisticated Policy Workflow Engine (PWE) that enables applications to offer a requirements-based tokenization implementation." - version: "2.4.2" + version: "2.5.0" contact: name: "API developer" url: "https://envisionblockchain.com" diff --git a/api-docs/package.json b/api-docs/package.json index 8e37a14abe..c0be71c645 100644 --- a/api-docs/package.json +++ b/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "api-docs", - "version": "2.4.2", + "version": "2.5.0-prerelease", "description": "Swagger Documentation", "main": "dist/index.js", "scripts": { @@ -23,5 +23,6 @@ "@types/swagger-ui-express": "^4.1.3", "tslint": "^6.1.3", "typescript": "^4.6.3" - } + }, + "stableVersion": "2.4.2" } diff --git a/api-gateway/package.json b/api-gateway/package.json index 5f3656c2a4..802d662c27 100644 --- a/api-gateway/package.json +++ b/api-gateway/package.json @@ -8,8 +8,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", - "@guardian/interfaces": "^2.4.2", + "@guardian/common": "^2.5.0-prerelease", + "@guardian/interfaces": "^2.5.0-prerelease", "dotenv": "^16.0.0", "express": "^4.17.1", "jszip": "^3.7.1", @@ -49,5 +49,6 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/api-gateway/src/api/service/tokens.ts b/api-gateway/src/api/service/tokens.ts index ba1c917d03..0fa45baeba 100644 --- a/api-gateway/src/api/service/tokens.ts +++ b/api-gateway/src/api/service/tokens.ts @@ -1,8 +1,8 @@ import { Guardians } from '@helpers/guardians'; import { permissionHelper } from '@auth/authorization-helper'; import { Response, Router } from 'express'; -import { ITokenInfo, UserRole } from '@guardian/interfaces'; -import { AuthenticatedRequest, IAuthUser, Logger } from '@guardian/common'; +import { IToken, ITokenInfo, UserRole } from '@guardian/interfaces'; +import { AuthenticatedRequest, Logger } from '@guardian/common'; import { PolicyEngine } from '@helpers/policy-engine'; import { findAllEntities } from '@helpers/utils'; import { TaskManager } from '@helpers/task-manager'; @@ -15,37 +15,70 @@ export const tokenAPI = Router(); /** * Connect policies to tokens * @param tokens - * @param user + * @param policies + * @param policyId */ -async function setTokensPolicies(tokens: any[], user: IAuthUser) { +function setTokensPolicies(tokens: any[], policies: any[], policyId?: any, notEmpty?: boolean): T[] { if (!tokens) { - return; + return []; } - const engineService = new PolicyEngine(); - - let result: any; - if (user.role === UserRole.STANDARD_REGISTRY) { - result = await engineService.getPolicies({ filters: { owner: user.did } }); - } else { - result = await engineService.getPolicies({ filters: { status: 'PUBLISH' } }); + for (const policyObject of policies) { + policyObject.tokenIds = findAllEntities(policyObject.config, 'tokenId'); } - const { policies } = result; - for (const token of tokens) { - const tokenPolicies = []; + token.policies = []; + token.policyIds = []; for (const policyObject of policies) { - const tokenIds = findAllEntities(policyObject.config, 'tokenId'); - if (tokenIds.includes(token.tokenId)) { - tokenPolicies.push(`${policyObject.name} (${policyObject.version || 'DRAFT'})`); + if (policyObject.tokenIds.includes(token.tokenId)) { + token.policies.push(`${policyObject.name} (${policyObject.version || 'DRAFT'})`); + token.policyIds.push(policyObject.id.toString()); } } - token.policies = tokenPolicies; } + if (policyId) { + tokens = tokens.filter(t => t.policyIds.includes(policyId)); + } + if (notEmpty) { + tokens = tokens.filter(t => !!t.policyIds.length); + } + return tokens; + } +tokenAPI.get('/', permissionHelper(UserRole.STANDARD_REGISTRY, UserRole.USER), async (req: AuthenticatedRequest, res: Response) => { + try { + const guardians = new Guardians(); + const engineService = new PolicyEngine(); + + const user = req.user; + const policyId = req.query?.policy; + + let tokens: IToken[] = []; + if (user.role === UserRole.STANDARD_REGISTRY) { + tokens = await guardians.getTokens({ did: user.did }); + const { policies } = await engineService.getPolicies({ filters: { owner: user.did } }); + tokens = setTokensPolicies(tokens, policies, policyId, false); + } else if (user.did) { + tokens = await guardians.getAssociatedTokens(user.did); + const { policies } = await engineService.getPolicies({ + filters: { + status: 'PUBLISH', + owner: user.parent + } + }); + tokens = setTokensPolicies(tokens, policies, policyId, true); + } + res.status(200).json(tokens); + } catch (error) { + new Logger().error(error, ['API_GATEWAY']); + res.status(500).send({ code: error.code || 500, message: error.message }); + } +}); + tokenAPI.post('/', permissionHelper(UserRole.STANDARD_REGISTRY), async (req: AuthenticatedRequest, res: Response) => { try { const guardians = new Guardians(); + const engineService = new PolicyEngine(); const user = req.user; if (!user.did) { @@ -58,13 +91,10 @@ tokenAPI.post('/', permissionHelper(UserRole.STANDARD_REGISTRY), async (req: Aut owner: user.did })); - tokens = await guardians.getTokens({ - did: user.did - }); + tokens = await guardians.getTokens({ did: user.did }); + const { policies } = await engineService.getPolicies({ filters: { owner: user.did } }); + tokens = setTokensPolicies(tokens, policies); - tokens = tokens || []; - - await setTokensPolicies(tokens, user); res.status(201).json(tokens); } catch (error) { new Logger().error(error, ['API_GATEWAY']); @@ -95,28 +125,6 @@ tokenAPI.post('/push/', permissionHelper(UserRole.STANDARD_REGISTRY), async (req res.status(201).send({ taskId, expectation }); }); -tokenAPI.get('/', permissionHelper(UserRole.STANDARD_REGISTRY, UserRole.USER), async (req: AuthenticatedRequest, res: Response) => { - try { - const guardians = new Guardians(); - const user = req.user; - let tokens = []; - - if (user.role === UserRole.STANDARD_REGISTRY) { - tokens = await guardians.getTokens({ - did: user.did - }); - } else if (user.did) { - tokens = await guardians.getAssociatedTokens(user.did); - } - tokens = tokens || []; - await setTokensPolicies(tokens, user); - res.status(200).json(tokens); - } catch (error) { - new Logger().error(error, ['API_GATEWAY']); - res.status(500).send({ code: error.code || 500, message: error.message }); - } -}); - tokenAPI.put('/:tokenId/associate', permissionHelper(UserRole.USER), async (req: AuthenticatedRequest, res: Response) => { try { const guardians = new Guardians(); diff --git a/api-gateway/src/api/service/websockets.ts b/api-gateway/src/api/service/websockets.ts index a2950684e9..b5e7a38fcc 100644 --- a/api-gateway/src/api/service/websockets.ts +++ b/api-gateway/src/api/service/websockets.ts @@ -54,10 +54,12 @@ export class WebSocketsService { private registerMessageHandler(): void { this.channel.response('update-block', async (msg) => { this.wss.clients.forEach((client: any) => { - this.send(client, { - type: 'update-event', - data: msg.uuid - }); + if (this.checkUserByDid(client, msg)) { + this.send(client, { + type: 'update-event', + data: msg.uuid + }); + } }); return new MessageResponse({}) }); @@ -89,15 +91,24 @@ export class WebSocketsService { return new MessageResponse({}); }); - this.channel.response('update-user-balance', async (msg) => { - this.wss.clients.forEach((client: any) => { - if (this.checkUserByName(client, msg)) { - this.send(client, { - type: 'PROFILE_BALANCE', - data: msg + this.channel.response('update-user-balance', async (msg) => { + this.wss.clients.forEach(client => { + new Users().getUserByAccount(msg.operatorAccountId).then(user => {{ + Object.assign(msg, { + user: user ? { + username: user.username, + did: user.did + } : null }); - } + if (this.checkUserByName(client, msg)) { + this.send(client, { + type: 'PROFILE_BALANCE', + data: msg + }); + } + }}); }); + return new MessageResponse({}); }); @@ -118,7 +129,6 @@ export class WebSocketsService { */ private registerConnection(): void { this.wss.on('connection', async (ws: any, req: IncomingMessage) => { - ws.user = await this.getUserByUrl(req.url); ws.on('message', async (data: Buffer) => { const message = data.toString(); if (message === 'ping') { @@ -128,6 +138,7 @@ export class WebSocketsService { this.wsResponse(ws, message); } }); + ws.user = await this.getUserByUrl(req.url); }); } @@ -228,7 +239,13 @@ export class WebSocketsService { * @private */ private checkUserByDid(client: any, msg: any): boolean { - return client && client.user && msg && msg.user && (client.user.did === msg.user.did); + if(client && client.user) { + if(msg && msg.user) { + return (client.user.did === msg.user.did || msg.user.virtual); + } + return true; + } + return false; } /** diff --git a/api-gateway/src/helpers/users.ts b/api-gateway/src/helpers/users.ts index 6d9d4635ff..ff8924fd83 100644 --- a/api-gateway/src/helpers/users.ts +++ b/api-gateway/src/helpers/users.ts @@ -75,6 +75,14 @@ export class Users extends ServiceRequestsBase { return await this.request(AuthEvents.GET_USER_BY_ID, {did}); } + /** + * Return user by account + * @param account + */ + public async getUserByAccount(account: string): Promise { + return await this.request(AuthEvents.GET_USER_BY_ACCOUNT, { account }); + } + /** * Return user by did * @param dids diff --git a/api-tests/index.js b/api-tests/index.js index ce4942bdce..c28bed9826 100644 --- a/api-tests/index.js +++ b/api-tests/index.js @@ -4,15 +4,12 @@ dotenv.config(); const { spawn } = require('child_process'); const kill = require('tree-kill'); const path = require('path'); -const fs = require('fs'); const { sleep, GenerateTokens } = require("./helpers"); const { Accounts } = require("./test-suits/accounts"); const { Profiles } = require("./test-suits/profiles"); -const { Schemas } = require("./test-suits/schemas"); const { Trustchains } = require("./test-suits/trustchains"); -const { Policies } = require("./test-suits/policies"); const processes = []; @@ -22,8 +19,8 @@ describe('Tests', async function () { this.timeout(10000000000); const pathArray = [ [path.resolve(path.join('..', 'logger-service')), {}], - [path.resolve(path.join('..', 'worker-service')), {}], - [path.resolve(path.join('..', 'auth-service')), {}], + [path.resolve(path.join('..', 'worker-service')), {IPFS_STORAGE_API_KEY: process.env.IPFS_STORAGE_API_KEY}], + [path.resolve(path.join('..', 'auth-service')), {HASHICORP_ADDRESS: `http://${process.env.HASHICORP_HOST}:${process.env.HASHICORP_PORT}`}], [path.resolve(path.join('..', 'ipfs-client')), {IPFS_STORAGE_API_KEY: process.env.IPFS_STORAGE_API_KEY}], [path.resolve(path.join('..', 'guardian-service')), {OPERATOR_ID: process.env.OPERATOR_ID, OPERATOR_KEY: process.env.OPERATOR_KEY}], [path.resolve(path.join('..', 'api-gateway')), {}] diff --git a/api-tests/test-suits/accounts.js b/api-tests/test-suits/accounts.js index 61506117f3..6974499ed4 100644 --- a/api-tests/test-suits/accounts.js +++ b/api-tests/test-suits/accounts.js @@ -160,10 +160,11 @@ function Accounts() { delete result.data.id; delete result.data._id; delete result.data.parent; + delete result.data.walletToken assert.deepEqual(result.data, { username: 'apiTest', did: null, - role: 'USER' + role: 'USER', }) }); } diff --git a/auth-service/.env b/auth-service/.env index 887b0dc297..631b9adf5f 100644 --- a/auth-service/.env +++ b/auth-service/.env @@ -3,3 +3,8 @@ SERVICE_CHANNEL="auth-service" ACCESS_TOKEN_SECRET="youraccesstokensecret" DB_HOST="localhost" DB_DATABASE="auth_db" +VAULT_PROVIDER="database" +HASHICORP_TOKEN="1234" +HASHICORP_ADDRESS="http://localhost:8200" +HASHICORP_NAMESPACE="admin" +#IMPORT_KEYS_FROM_DB=1 diff --git a/auth-service/.env.docker b/auth-service/.env.docker index ae0c3bdbe7..63439530a5 100644 --- a/auth-service/.env.docker +++ b/auth-service/.env.docker @@ -3,3 +3,7 @@ SERVICE_CHANNEL="auth-service" ACCESS_TOKEN_SECRET="youraccesstokensecret" DB_HOST="mongo" DB_DATABASE="auth_db" +VAULT_PROVIDER="database" +HASHICORP_TOKEN="1234" +HASHICORP_ADDRESS="http://vault:8200" +HASHICORP_NAMESPACE="admin" diff --git a/auth-service/package.json b/auth-service/package.json index 80425a2bc2..7608632172 100644 --- a/auth-service/package.json +++ b/auth-service/package.json @@ -6,19 +6,21 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", - "@guardian/interfaces": "^2.4.2", + "@guardian/common": "^2.5.0-prerelease", + "@guardian/interfaces": "^2.5.0-prerelease", "@mikro-orm/core": "^5.3.0", "@mikro-orm/mongodb": "^5.3.0", "dotenv": "^16.0.0", "jsonwebtoken": "^8.5.1", "module-alias": "^2.2.2", + "node-vault": "^0.9.22", "reflect-metadata": "^0.1.13" }, "description": "", "devDependencies": { "@types/jsonwebtoken": "^8.5.4", "@types/node": "^17.0.13", + "@types/node-vault": "^0", "chai": "^4.3.4", "cross-env": "^7.0.3", "mocha": "^9.2.0", @@ -44,5 +46,6 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/auth-service/src/api/account-service.ts b/auth-service/src/api/account-service.ts index c345d97e97..31c28b77e4 100644 --- a/auth-service/src/api/account-service.ts +++ b/auth-service/src/api/account-service.ts @@ -71,6 +71,7 @@ export class AccountService { username, password: passwordDigest, role, + walletToken: crypto.createHash('sha1').update(Math.random().toString()).digest('hex'), parent: null, did: null }); diff --git a/auth-service/src/api/wallet-service.ts b/auth-service/src/api/wallet-service.ts index ac14ded66f..4da0a0588f 100644 --- a/auth-service/src/api/wallet-service.ts +++ b/auth-service/src/api/wallet-service.ts @@ -1,12 +1,17 @@ -import { WalletAccount } from '@entity/wallet-account'; import { MessageBrokerChannel, MessageResponse, MessageError, Logger, - DataBaseHelper } from '@guardian/common'; -import { WalletEvents, IGetKeyMessage, ISetKeyMessage } from '@guardian/interfaces'; +import { + WalletEvents, + IGetKeyMessage, + ISetKeyMessage, + IGetKeyResponse, + IGetGlobalApplicationKey, ISetGlobalApplicationKey +} from '@guardian/interfaces'; +import { IVault } from '../vaults'; /** * Wallet service @@ -14,38 +19,58 @@ import { WalletEvents, IGetKeyMessage, ISetKeyMessage } from '@guardian/interfac export class WalletService { constructor( private readonly channel: MessageBrokerChannel, - ) { - // this.registerListeners(); - } + private readonly vault: IVault + ) { } /** * Register listeners */ registerListeners(): void { - this.channel.response(WalletEvents.GET_KEY, async (msg) => { + this.channel.response(WalletEvents.GET_KEY, async (msg) => { const { token, type, key } = msg; try { - return new MessageResponse(await new DataBaseHelper(WalletAccount).findOne({ token, type: type + '|' + key })); + const value = await this.vault.getKey(token, type, key); + return new MessageResponse({ key: value }); } catch (error) { new Logger().error(error, ['AUTH_SERVICE']); return new MessageError(error) } }); - this.channel.response(WalletEvents.SET_KEY, async (msg) => { + this.channel.response(WalletEvents.SET_KEY, async (msg) => { const { token, type, key, value } = msg; try { - const walletAcc = new DataBaseHelper(WalletAccount).create({ - token, - type: type + '|' + key, - key: value - }); - return new MessageResponse(await new DataBaseHelper(WalletAccount).save(walletAcc)); + await this.vault.setKey(token, type, key, value); + return new MessageResponse(null); } catch (error) { new Logger().error(error, ['AUTH_SERVICE']); - return new MessageError(error) + return new MessageError(error); + } + }); + + this.channel.response(WalletEvents.GET_GLOBAL_APPLICATION_KEY, async (msg) => { + const {type} = msg; + + try { + const key = await this.vault.getGlobalApplicationKey(type); + return new MessageResponse({key}); + } catch (error) { + new Logger().error(error, ['AUTH_SERVICE']); + return new MessageError(error); + } + }); + + this.channel.response(WalletEvents.SET_GLOBAL_APPLICATION_KEY, async (msg) => { + const {type, key} = msg; + + try { + await this.vault.setGlobalApplicationKey(type, key); + return new MessageResponse(null); + } catch (error) { + new Logger().error(error, ['AUTH_SERVICE']); + return new MessageError(error); } }); } diff --git a/auth-service/src/app.ts b/auth-service/src/app.ts index dca728665a..e6894169ca 100644 --- a/auth-service/src/app.ts +++ b/auth-service/src/app.ts @@ -5,6 +5,8 @@ import { ApplicationState, MessageBrokerChannel, Logger, DB_DI, Migration, COMMO import { ApplicationStates } from '@guardian/interfaces'; import { MikroORM } from '@mikro-orm/core'; import { MongoDriver } from '@mikro-orm/mongodb'; +import { InitializeVault } from './vaults'; +import { ImportKeysFromDatabase } from '@helpers/import-keys-from-database'; Promise.all([ Migration({ @@ -22,17 +24,23 @@ Promise.all([ ensureIndexes: true }), MessageBrokerChannel.connect('LOGGER_SERVICE'), -]).then(async ([_, db, cn]) => { + InitializeVault(process.env.VAULT_PROVIDER) +]).then(async ([_, db, cn, vault]) => { DB_DI.orm = db; const state = new ApplicationState('AUTH_SERVICE'); const channel = new MessageBrokerChannel(cn, 'auth-service'); + state.setChannel(channel); state.updateState(ApplicationStates.INITIALIZING); await fixtures(); new Logger().setChannel(channel); new AccountService(channel).registerListeners(); - new WalletService(channel).registerListeners(); + new WalletService(channel, vault).registerListeners(); + + if (process.env.IMPORT_KEYS_FROM_DB) { + await ImportKeysFromDatabase(vault); + } state.updateState(ApplicationStates.READY); new Logger().info('auth service started', ['AUTH_SERVICE']); diff --git a/auth-service/src/helpers/import-keys-from-database.ts b/auth-service/src/helpers/import-keys-from-database.ts new file mode 100644 index 0000000000..0c0a834f6c --- /dev/null +++ b/auth-service/src/helpers/import-keys-from-database.ts @@ -0,0 +1,44 @@ +import { WalletAccount } from '@entity/wallet-account'; +import { DataBaseHelper, Logger } from '@guardian/common'; +import { IVault } from '../vaults'; + +/** + * Migration function + * @constructor + */ +export async function ImportKeysFromDatabase(vault: IVault): Promise { + const logger = new Logger(); + + if (process.env.VAULT_PROVIDER === 'database') { + logger.error('Cannot import to database provider', ['AUTH_SERVICE']); + return; + } + + const re = /^KEY\|(.+)$/; + + logger.info('Start import keys', ['AUTH_SERVICE']); + const walletRepository = new DataBaseHelper(WalletAccount) + const databaseWallets = await walletRepository.find({ + type: re + }); + const walletAccounts = databaseWallets.map(w => { + return { + value: w.key, + token: w.token, + type: 'KEY', + key: re.exec(w.type)[1] + } + }) + + logger.info(`found ${walletAccounts.length} keys`, ['AUTH_SERVICE']); + + try { + for (const {token, type, key, value} of walletAccounts) { + await vault.setKey(token, type, key, value); + } + logger.info(`${walletAccounts.length} keys was added to ${vault.constructor.name.toUpperCase()}`, ['AUTH_SERVICE']); + } catch (e) { + logger.error(`${vault.constructor.name.toUpperCase()} vault import error: ${e.message}`, ['AUTH_SERVICE']); + } + +} diff --git a/auth-service/src/vaults/index.ts b/auth-service/src/vaults/index.ts new file mode 100644 index 0000000000..96570b3bcd --- /dev/null +++ b/auth-service/src/vaults/index.ts @@ -0,0 +1,3 @@ +export * from './vault-providers'; +export { InitializeVault } from './initialize-vault'; +export { IVault } from './vault.interface'; diff --git a/auth-service/src/vaults/initialize-vault.ts b/auth-service/src/vaults/initialize-vault.ts new file mode 100644 index 0000000000..c3db784540 --- /dev/null +++ b/auth-service/src/vaults/initialize-vault.ts @@ -0,0 +1,24 @@ +import { IVault } from './vault.interface'; +import * as vaultProviders from './vault-providers' +import assert from 'assert'; + +/** + * Vault service factory + * @constructor + * @param providerName + */ +export async function InitializeVault(providerName: string): Promise { + let constructor: new () => IVault; + for (const [name, c] of Object.entries(vaultProviders)) { + if (providerName.toLowerCase() === name.toLowerCase()) { + constructor = c; + break; + } + } + assert(constructor, `Provider '${providerName}' was not registered`); + + const v = new constructor(); + await v.init(); + + return v; +} diff --git a/auth-service/src/vaults/vault-providers/database.ts b/auth-service/src/vaults/vault-providers/database.ts new file mode 100644 index 0000000000..0bced775b1 --- /dev/null +++ b/auth-service/src/vaults/vault-providers/database.ts @@ -0,0 +1,65 @@ +import { IVault } from '../vault.interface'; +import { DataBaseHelper } from '@guardian/common'; +import { WalletAccount } from '@entity/wallet-account'; + +/** + * Database vault + */ +export class Database implements IVault { + + /** + * Init vault + */ + async init() { + return this; + } + /** + * Get key from vault + * @param token + * @param type + * @param key + */ + async getKey(token: string, type: string, key: string): Promise { + const item = await new DataBaseHelper(WalletAccount).findOne({ token, type: type + '|' + key }); + return item?.key + } + + /** + * Set key to vault + * @param token + * @param type + * @param key + * @param value + */ + async setKey(token: string, type: string, key: string, value: string): Promise { + const walletAcc = new DataBaseHelper(WalletAccount).create({ + token, + type: type + '|' + key, + key: value + }); + await new DataBaseHelper(WalletAccount).save(walletAcc); + } + + /** + * Get global application key + * @param type + */ + async getGlobalApplicationKey(type: string): Promise { + const item = await new DataBaseHelper(WalletAccount).findOne({ type }); + return item?.key; + } + + /** + * Set global application key + * @param type + * @param key + */ + async setGlobalApplicationKey(type: string, key: string): Promise { + let setting: WalletAccount = await new DataBaseHelper(WalletAccount).findOne({ type }); + if (!setting) { + setting = new DataBaseHelper(WalletAccount).create({ type, key }); + } + setting.key = key; + await new DataBaseHelper(WalletAccount).save(setting); + } +} diff --git a/auth-service/src/vaults/vault-providers/hashicorp.ts b/auth-service/src/vaults/vault-providers/hashicorp.ts new file mode 100644 index 0000000000..af7b316fff --- /dev/null +++ b/auth-service/src/vaults/vault-providers/hashicorp.ts @@ -0,0 +1,125 @@ +import NodeVault from 'node-vault'; +import { IVault } from '../vault.interface'; +import assert from 'assert'; +import crypto from 'crypto'; + +/** + * HashiCorp vault helper + */ +export class Hashicorp implements IVault{ + /** + * Vault options + * @private + */ + private readonly options: NodeVault.VaultOptions = { + apiVersion: 'v1', + endpoint: process.env.HASHICORP_ADDRESS, + token: process.env.HASHICORP_TOKEN, + namespace: process.env.HASHICORP_NAMESPACE + } + + /** + * Vault client instance + * @private + */ + private readonly vault: NodeVault.client; + + /** + * Logger instance + * @private + */ + // private readonly logger: Logger; + + constructor() { + assert(process.env.HASHICORP_ADDRESS, 'HASHICORP_ADDRESS environment variable is not set'); + assert(process.env.HASHICORP_TOKEN, 'HASHICORP_TOKEN environment variable is not set'); + + this.vault = NodeVault(this.options); + // this.logger = new Logger(); + } + + /** + * Generate base64 encoded string + * @param token + * @param type + * @param key + * @private + */ + private generateKeyName(token: string, type: string, key: string): string { + return crypto.createHash('sha512').update(`${token}|${type}|${key}`).digest('hex'); + } + + /** + * Init vault + * @private + */ + public async init(): Promise { + try { + const {initialized} = await this.vault.initialized(); + if (!initialized) { + const {keys, root_token} = await this.vault.init({secret_shares: 1, secret_threshold: 1}); + this.vault.token = root_token; + console.info('Root Token', root_token); + await this.vault.unseal({secret_shares: 1, key: keys[0]}); + + } + } catch (e) { + console.warn(e.message); + } + + return this; + } + + /** + * Get key from vault + * @param token + * @param type + * @param key + */ + public async getKey(token: string, type: string, key: string): Promise { + const result = await this.vault.read(`secret/data/${this.generateKeyName(token, type, key)}`); + return result.data.data.privateKey; + } + + /** + * Set key to vault + * @param token + * @param type + * @param key + * @param value + */ + public async setKey(token: string, type: string, key: string, value: string): Promise{ + await this.vault.write(`secret/data/${this.generateKeyName(token, type, key)}`, { + data: { + privateKey: value + } + }) + } + + /** + * Get global application key + * @param type + */ + async getGlobalApplicationKey(type: string): Promise { + try { + const result = await this.vault.read(`secret/data/${type}`); + return result.data.data.settingKey; + } catch (e) { + console.warn(e.message); + return undefined; + } + } + + /** + * Set global application key + * @param type + * @param key + */ + async setGlobalApplicationKey(type: string, key: string): Promise { + await this.vault.write(`secret/data/${type}`, { + data: { + settingKey: key + } + }) + } +} diff --git a/auth-service/src/vaults/vault-providers/index.ts b/auth-service/src/vaults/vault-providers/index.ts new file mode 100644 index 0000000000..e048b9071d --- /dev/null +++ b/auth-service/src/vaults/vault-providers/index.ts @@ -0,0 +1,2 @@ +export { Hashicorp } from './hashicorp'; +export { Database } from './database'; diff --git a/auth-service/src/vaults/vault.interface.ts b/auth-service/src/vaults/vault.interface.ts new file mode 100644 index 0000000000..64209517e5 --- /dev/null +++ b/auth-service/src/vaults/vault.interface.ts @@ -0,0 +1,38 @@ +/** + * Vaults interface + */ +export interface IVault { + /** + * Get key + * @param token + * @param type + * @param key + */ + getKey: (token: string, type: string, key: string) => Promise; + /** + * Set key + * @param token + * @param type + * @param key + * @param value + */ + setKey: (token: string, type: string, key: string, value: string) => Promise; + + /** + * Get global application key + * @param type + */ + getGlobalApplicationKey: (type: string) => Promise; + + /** + * Set global application key + * @param type + * @param key + */ + setGlobalApplicationKey: (type: string, key: string) => Promise; + + /** + * Initiate vault connection + */ + init: () => Promise +} diff --git a/common/package.json b/common/package.json index f5c2bc7968..c08b616113 100644 --- a/common/package.json +++ b/common/package.json @@ -1,7 +1,7 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/interfaces": "^2.4.2", + "@guardian/interfaces": "^2.5.0-prerelease", "@mikro-orm/core": "^5.3.0", "@mikro-orm/migrations-mongodb": "^5.3.1", "@mikro-orm/mongodb": "^5.3.0", @@ -34,5 +34,6 @@ "prepare": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/common/src/helpers/index.ts b/common/src/helpers/index.ts index 7e41bf56f9..894d4f961a 100644 --- a/common/src/helpers/index.ts +++ b/common/src/helpers/index.ts @@ -1,4 +1,6 @@ export * from './application-state'; export * from './logger'; export * from './db-helper'; -export * from './migration'; \ No newline at end of file +export * from './migration'; +export * from './settings-container'; +export * from './service-requests-base'; diff --git a/common/src/helpers/service-requests-base.ts b/common/src/helpers/service-requests-base.ts new file mode 100644 index 0000000000..4040ba5a36 --- /dev/null +++ b/common/src/helpers/service-requests-base.ts @@ -0,0 +1,53 @@ +import { MessageBrokerChannel } from '../mq'; + +/** + * Service request base class + */ +export abstract class ServiceRequestsBase { + /** + * Message broker channel + * @private + */ + protected channel: MessageBrokerChannel; + /** + * Message broker target + * @private + */ + abstract readonly target: string; + + /** + * Register channel + * @param channel + */ + public setChannel(channel: MessageBrokerChannel) { + this.channel = channel; + } + + /** + * Get channel + */ + public getChannel(): MessageBrokerChannel { + return this.channel; + } + + /** + * Request to guardian service method + * @param entity + * @param params + * @param type + */ + public async request(entity: string, params?: any, type?: string): Promise { + try { + const response = await this.channel.request(`${this.target}.${entity}`, params); + if (!response) { + throw new Error('Server is not available'); + } + if (response.error) { + throw new Error(response.error); + } + return response.body; + } catch (error) { + throw new Error(`Guardian (${entity}) send: ` + error); + } + } +} diff --git a/common/src/helpers/settings-container.ts b/common/src/helpers/settings-container.ts new file mode 100644 index 0000000000..8c5db956bc --- /dev/null +++ b/common/src/helpers/settings-container.ts @@ -0,0 +1,93 @@ +import { Singleton } from '../decorators/singleton'; +import { ServiceRequestsBase } from './service-requests-base'; +import { Logger } from './logger'; +import { IGetKeyResponse, WalletEvents } from '@guardian/interfaces'; + +/** + * Application settings container + */ +@Singleton +export class SettingsContainer extends ServiceRequestsBase { + /** + * Message broker target + */ + public target: string = 'auth-service' + + /** + * Initialized flag + * @private + */ + private initialized: boolean; + + /** + * Settings object + * @private + */ + private readonly _settings: {[key: string]: string}; + + /** + * Settings getter + */ + public get settings(): {[key: string]: string} { + if (!this.initialized) { + throw new Error('Settings container was not initialized'); + } + return this._settings; + } + + constructor() { + super(); + this.initialized = false; + this._settings = {}; + } + + /** + * Initialize settings + */ + public async init(...settings: string[]): Promise { + if (this.initialized) { + throw new Error('Settings already initialized'); + } + + for (const setting of settings) { + this._settings[setting] = await this.getGlobalApplicationKey(setting); + + if (!this._settings[setting] && process.env[setting]) { + await this.setGlobalApplicationKey(setting, process.env[setting]); + await new Logger().info(`${setting} was set from environment`, ['GUARDIAN_SERVICE']); + } + } + + this.initialized = true; + } + + /** + * Update key + * @param name + * @param value + */ + public async updateSetting(name: string, value: string): Promise { + if (!Object.keys(this._settings).includes(name)) { + throw new Error(`${name} setting was not registered`); + } + await this.setGlobalApplicationKey(name, value); + } + + /** + * Get global application key + * @param type + */ + private async getGlobalApplicationKey(type: string): Promise { + const item = await this.request(WalletEvents.GET_GLOBAL_APPLICATION_KEY, { type }); + return item.key + } + + /** + * Set global application key + * @param type + */ + private async setGlobalApplicationKey(type: string, key: string): Promise { + await this.request(WalletEvents.SET_GLOBAL_APPLICATION_KEY, { type, key }); + this._settings[type] = key; + } +} diff --git a/common/src/mq/message-broker-channel.ts b/common/src/mq/message-broker-channel.ts index 253a6ea1b3..2f2761ac5c 100644 --- a/common/src/mq/message-broker-channel.ts +++ b/common/src/mq/message-broker-channel.ts @@ -117,7 +117,7 @@ export class MessageBrokerChannel { this.channel.request(eventType, StringCodec().encode(stringPayload), { timeout: timeout || MQ_TIMEOUT, headers: head - }).then(() => { return; }, (error) => { + }).then(() => null, (error) => { reqMap.delete(messageId); // Nats no subscribe error diff --git a/docker-compose.yml b/docker-compose.yml index ba4a0ad038..f0a7cba65b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,21 @@ services: - '8222:8222' command: '--http_port 8222' + vault: + image: vault + expose: + - 8200 + ports: + - '8200:8200' + environment: + VAULT_SERVER: "http://0.0.0.0:8200" + VAULT_DEV_ROOT_TOKEN_ID: "1234" + cap_add: + - IPC_LOCK + volumes: + - ./file:/vault/file:rw + - ./config:/vault/config:rw + logger-service: build: context: . @@ -52,6 +67,7 @@ services: dockerfile: ./auth-service/Dockerfile depends_on: - mongo + - vault - message-broker - logger-service diff --git a/docs/.gitbook/assets/API_10 (1) (1) (1) (1).png b/docs/.gitbook/assets/API_10 (1) (1) (1) (1).png new file mode 100644 index 0000000000..9f1a84da4d Binary files /dev/null and b/docs/.gitbook/assets/API_10 (1) (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/API_10 (1) (1) (1).png b/docs/.gitbook/assets/API_10 (1) (1) (1).png new file mode 100644 index 0000000000..9f1a84da4d Binary files /dev/null and b/docs/.gitbook/assets/API_10 (1) (1) (1).png differ diff --git a/docs/.gitbook/assets/image (1) (3) (1).png b/docs/.gitbook/assets/image (1) (3) (1).png new file mode 100644 index 0000000000..c22c541248 Binary files /dev/null and b/docs/.gitbook/assets/image (1) (3) (1).png differ diff --git a/docs/.gitbook/assets/image (1) (3).png b/docs/.gitbook/assets/image (1) (3).png index c22c541248..bfc7de1b26 100644 Binary files a/docs/.gitbook/assets/image (1) (3).png and b/docs/.gitbook/assets/image (1) (3).png differ diff --git a/docs/.gitbook/assets/image (1).png b/docs/.gitbook/assets/image (1).png index bfc7de1b26..e40832f472 100644 Binary files a/docs/.gitbook/assets/image (1).png and b/docs/.gitbook/assets/image (1).png differ diff --git a/docs/.gitbook/assets/image (13) (4).png b/docs/.gitbook/assets/image (13) (4).png new file mode 100644 index 0000000000..b3a52d012c Binary files /dev/null and b/docs/.gitbook/assets/image (13) (4).png differ diff --git a/docs/.gitbook/assets/image (13).png b/docs/.gitbook/assets/image (13).png index b3a52d012c..3e92f1dbdb 100644 Binary files a/docs/.gitbook/assets/image (13).png and b/docs/.gitbook/assets/image (13).png differ diff --git a/docs/.gitbook/assets/image (19) (2).png b/docs/.gitbook/assets/image (19) (2).png new file mode 100644 index 0000000000..65926b06c5 Binary files /dev/null and b/docs/.gitbook/assets/image (19) (2).png differ diff --git a/docs/.gitbook/assets/image (19).png b/docs/.gitbook/assets/image (19).png index 65926b06c5..20f09e8a5b 100644 Binary files a/docs/.gitbook/assets/image (19).png and b/docs/.gitbook/assets/image (19).png differ diff --git a/docs/.gitbook/assets/image (20) (1).png b/docs/.gitbook/assets/image (20) (1).png new file mode 100644 index 0000000000..aea54690f1 Binary files /dev/null and b/docs/.gitbook/assets/image (20) (1).png differ diff --git a/docs/.gitbook/assets/image (20).png b/docs/.gitbook/assets/image (20).png index aea54690f1..4251798577 100644 Binary files a/docs/.gitbook/assets/image (20).png and b/docs/.gitbook/assets/image (20).png differ diff --git a/docs/.gitbook/assets/image (21) (1).png b/docs/.gitbook/assets/image (21) (1).png new file mode 100644 index 0000000000..8af8beafdd Binary files /dev/null and b/docs/.gitbook/assets/image (21) (1).png differ diff --git a/docs/.gitbook/assets/image (21).png b/docs/.gitbook/assets/image (21).png index 8af8beafdd..4251798577 100644 Binary files a/docs/.gitbook/assets/image (21).png and b/docs/.gitbook/assets/image (21).png differ diff --git a/docs/.gitbook/assets/image (29) (2).png b/docs/.gitbook/assets/image (29) (2).png new file mode 100644 index 0000000000..0f096e7e6d Binary files /dev/null and b/docs/.gitbook/assets/image (29) (2).png differ diff --git a/docs/.gitbook/assets/image (29).png b/docs/.gitbook/assets/image (29).png index 0f096e7e6d..45f9ece7d8 100644 Binary files a/docs/.gitbook/assets/image (29).png and b/docs/.gitbook/assets/image (29).png differ diff --git a/docs/.gitbook/assets/image (31) (2).png b/docs/.gitbook/assets/image (31) (2).png new file mode 100644 index 0000000000..2b04d0a172 Binary files /dev/null and b/docs/.gitbook/assets/image (31) (2).png differ diff --git a/docs/.gitbook/assets/image (31).png b/docs/.gitbook/assets/image (31).png index 2b04d0a172..791d8fbf8b 100644 Binary files a/docs/.gitbook/assets/image (31).png and b/docs/.gitbook/assets/image (31).png differ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index fa7957eb61..4a715d2ff8 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -95,6 +95,7 @@ * [mintDocumentBlock](available-policy-workflow-blocks/mintdocumentblock.md) * [Events](available-policy-workflow-blocks/events.md) * [groupManagerBlock](available-policy-workflow-blocks/groupmanagerblock.md) +* [multiSignBlock](available-policy-workflow-blocks/multisignblock.md) ## Policy Workflow Creation using the Guardian User Interface diff --git a/docs/available-policy-workflow-blocks/groupmanagerblock.md b/docs/available-policy-workflow-blocks/groupmanagerblock.md index e501561c16..4e2e567ce5 100644 --- a/docs/available-policy-workflow-blocks/groupmanagerblock.md +++ b/docs/available-policy-workflow-blocks/groupmanagerblock.md @@ -4,8 +4,6 @@ This block allows to manage group membership, add and remove users from the grou ### 1. Properties - - | Block Property | Definition | Example Input | Status | | ---------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------ | | tag | Unique name for the logic block. | groupManagerBlock | | @@ -24,7 +22,7 @@ This block allows to manage group membership, add and remove users from the grou #### 2.1 **List of the groups in which the user is included:** -
+
#### **2.2 List of the users included in the group** @@ -42,4 +40,4 @@ Next step is to copy and send the unique invite or the link to the invite. #### 2.4 **Removing users from groups** -
+
diff --git a/docs/available-policy-workflow-blocks/interfacedocumentssourceblock.md b/docs/available-policy-workflow-blocks/interfacedocumentssourceblock.md index b61de6245c..dd063b918c 100644 --- a/docs/available-policy-workflow-blocks/interfacedocumentssourceblock.md +++ b/docs/available-policy-workflow-blocks/interfacedocumentssourceblock.md @@ -17,22 +17,23 @@ RefreshEvents are used to refreshing the UI, instead of "dependencies" property. ### UI Properties -| UI Property | Definition | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Title | Type of the displayed value, possible options. Current options are: TEXT (ordinary text), BUTTON (a button), or BLOCK (a block embedded into the column). | -| Field Name | Object fields to retrieve the values from. Internal fields are separated by ".", access to array elements is via index. This is the field name. | -| Field Type | Current Options: TEXT, BUTTON, AND BLOCK. | -| Field Title | Title of the column. | -| Field Tooltip | Provide a tooltip for the field. | -| Field Cell Content | Content inside the cell. | -| Field UI Class | Arbitrary Class | -| Width | For example : 100px | -| Bind Group | If interfaceDocumentsSourceBlock has more than one documentsSourceAddon, then you can create different columns for each (names must be the same) | -| Bind Block | Specifying a "bindBlock" field would result in the display of the linked block in side the dialog.. Needs for the field type to be a BLOCK or BUTTON with the Action type as DIALOGUE. | -| Action | Needs for the field type to be a BUTTON. Specifies what action will happen when the button is clicked. Action options are currently: LINK to a URL or prompt a DIALOGUE box. | -| Dialogue Type | Currently only json type is supported. Needs for the field type to be a BUTTON and Action to be DIALOGUE. | -| Dialogue Content | Provide content for the dialogue box. Needs for the field type to be a BUTTON and Action to be DIALOGUE. | -| Dialogue Class | Dialog style. Needs for the field type to be a BUTTON and Action to be DIALOGUE. | +| UI Property | Definition | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Title | Type of the displayed value, possible options. Current options are: TEXT (ordinary text), BUTTON (a button), or BLOCK (a block embedded into the column). | +| Enable common sorting |

  1. When it is true, user can sort grid data on UI side, or make POST request to interfaceSourceBlock with body ({ orderField: 'option.status', orderDirection: 'asc'}) to change sorting through the API
  2. When it is false, user can set order to specific documentSourceAddon block by POST request with same body through the API
| +| Field Name | Object fields to retrieve the values from. Internal fields are separated by ".", access to array elements is via index. This is the field name. | +| Field Type | Current Options: TEXT, BUTTON, AND BLOCK. | +| Field Title | Title of the column. | +| Field Tooltip | Provide a tooltip for the field. | +| Field Cell Content | Content inside the cell. | +| Field UI Class | Arbitrary Class | +| Width | For example : 100px | +| Bind Group | If interfaceDocumentsSourceBlock has more than one documentsSourceAddon, then you can create different columns for each (names must be the same) | +| Bind Block | Specifying a "bindBlock" field would result in the display of the linked block in side the dialog.. Needs for the field type to be a BLOCK or BUTTON with the Action type as DIALOGUE. | +| Action | Needs for the field type to be a BUTTON. Specifies what action will happen when the button is clicked. Action options are currently: LINK to a URL or prompt a DIALOGUE box. | +| Dialogue Type | Currently only json type is supported. Needs for the field type to be a BUTTON and Action to be DIALOGUE. | +| Dialogue Content | Provide content for the dialogue box. Needs for the field type to be a BUTTON and Action to be DIALOGUE. | +| Dialogue Class | Dialog style. Needs for the field type to be a BUTTON and Action to be DIALOGUE. | ### Events diff --git a/docs/available-policy-workflow-blocks/introduction.md b/docs/available-policy-workflow-blocks/introduction.md index ba58330492..ec3abf8f2b 100644 --- a/docs/available-policy-workflow-blocks/introduction.md +++ b/docs/available-policy-workflow-blocks/introduction.md @@ -35,3 +35,5 @@ Starting with the [Wikipedia definition](https://en.wikipedia.org/wiki/Workflow\ | tokenConfirmationBlock | Block enables the owner of the private key for the account to manually perform operations with the token | [tokenconfirmationblock.md](tokenconfirmationblock.md "mention") | | mintDocumentBlock | Block is responsible for adding configurations on calculating the amount of tokens to be minted. | [mintdocumentblock.md](mintdocumentblock.md "mention") | | groupManagerBlock | Block allows to manage group membership, add and remove users from the group. | [groupmanagerblock.md](groupmanagerblock.md "mention") | +| multiSignBlock | This block provides a way to specify multiple signators for a single VC document, and then create a VP based on it. | [groupmanagerblock.md](groupmanagerblock.md "mention") | +| | | | diff --git a/docs/available-policy-workflow-blocks/mintdocumentblock.md b/docs/available-policy-workflow-blocks/mintdocumentblock.md index ef1aa2774c..d72cdb693d 100644 --- a/docs/available-policy-workflow-blocks/mintdocumentblock.md +++ b/docs/available-policy-workflow-blocks/mintdocumentblock.md @@ -22,7 +22,7 @@ This block is responsible for adding configurations on calculating the amount of | Account Id (Field) | The value from this field is used as the ID of the account under which the action is performed when ‘Account Type’ is set to ‘Custom’. | | Memo | The value in this filed is used to customize the Memo field name. | -
+
{% hint style="info" %} **Notes:** diff --git a/docs/available-policy-workflow-blocks/multisignblock.md b/docs/available-policy-workflow-blocks/multisignblock.md new file mode 100644 index 0000000000..8843bd9988 --- /dev/null +++ b/docs/available-policy-workflow-blocks/multisignblock.md @@ -0,0 +1,111 @@ +# multiSignBlock + +This block provides a way to specify multiple signators for a single VC document, and then create a VP based on it. + +### 1. Properties + +| Block Property | Definition | Example Input | Status | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------ | +| tag | Unique name for the logic block. | multiSignBlock | | +| permissions | Which entity has rights to interact at this part of the workflow. | NoRole | | +| defaultActive | Shows whether this block is active at this time and whether it needs to be shown. | Checked or unchecked. | | +| On errors | Called if the system error has occurs in the Block |

  • No action
  • Retry
  • Go to step
  • Go to tag
| | +| Stop Propagation | End processing here, don't pass control to the next block. | Checked or unchecked. | | +| Threshold (%) | Proportion Of signators which are required to sign the document to achieve quorum for it to transition to ‘signed’ status. Must be a number between 0 and 100. | 0-100 | | + +{% hint style="info" %} +**Note:** The system assigns ‘not signed’ status to the document when 100 – threshold percentage of users indicated rejection status. +{% endhint %} + +### 2. Events + +| Event | Description | Content | +| ----------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- | +| SignatureQuorumReachedEvent | This event occurs when the threshold number (quorum) of signatures has been reached. | now-signed target VC document which can then be used for further processing | +| SignatureSetInsufficientEvent | This event occurs when the threshold number (quorum) of rejections has been achieved | rejected (target) VC document which can be used for further processing | + +### 3. Data Format + +#### 3.1 POST request + +``` +{ + "document":{ + "id":"…" – ID of the VC document + }, + "status":"SIGNED" – new status, can be SIGNED or DECLINED +} + +``` + +#### 3.2 GET request + +``` +{ + blockType: "multiSignBlock" + id:"61ed0335-8b7e-44d9-aedd-0c86c5806442" + status: { + confirmationStatus: - final status, it is ‘null’ if the quorum is not reached + data: [ + { + username, + did – did and username of the user which took the decision + status – the decision of the user, the value space is: SIGNED/DECLINED + } + ] + } +} + +``` + +#### Array of the decisions for each user can be as follows: + +| Type of Decision | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------- | +| declinedCount | number of users who declined signing the document | +| declinedPercent | percentage of users who declined need signing the document | +| declinedThreshold | threshold number of users who need to decline signing the document to reach the final decision | +| documentStatus | status of the document for the current users, null if the user has not made a selection to sign or decline | +| signedCount | number of users who have signed the document | +| signedPercent | percentage of users who have signed the document | +| signedThreshold | threshold number of users who need to sign the document to reach the final decision | +| threshold | threshold in terms of percentage | +| total | total number of users in the signing group | + +### 4. Example + +#### 4.1 Important Points + +4.1.1 multiSignBlock must be used with Groups. + +
+ +4.1.2 multiSignBlock must be child block of grid block to receive all data it requires to operate. + +
+ +### 5. UI + +#### 5.1 Signing the document + +We have an option of Signing/ Declining the document by clicking on "Sign" or "Decline" button for the document as shown below: + +
+ +#### 5.2 Threshold Display + +Number of users, who have signed or declined the document can be displayed with threshold as shown below: + +
+ +#### 5.3 Detailed Signature Information + +To get detailed information on Signature status, we have an info icon near the threshold as shown below: + +
+ +#### 5.4 Final Signature Result + +To get the final Signature Result with detailed information such as which users have Signed / Declined, we need to hover on the Status as shown below: + +
diff --git a/docs/available-policy-workflow-blocks/tokenconfirmationblock.md b/docs/available-policy-workflow-blocks/tokenconfirmationblock.md index 05762ed0ec..5ca10bc299 100644 --- a/docs/available-policy-workflow-blocks/tokenconfirmationblock.md +++ b/docs/available-policy-workflow-blocks/tokenconfirmationblock.md @@ -30,7 +30,7 @@ This block enables the owner of the private key for the account to manually perf ![JSON View of the Block](<../.gitbook/assets/image (9).png>) -![Configuring tokenConfirmationBlock](<../.gitbook/assets/image (1) (3).png>) +![Configuring tokenConfirmationBlock](<../.gitbook/assets/image (1) (3) (1).png>) ![Creating Event to move to next step](<../.gitbook/assets/image (8).png>) @@ -43,4 +43,3 @@ The user need to input the private key for the account to enable Guardian to per {% endhint %} ![](<../.gitbook/assets/image (17) (2).png>) - diff --git a/docs/available-schema-types/available-schema-types.md b/docs/available-schema-types/available-schema-types.md index 6ee52a743f..c80e7d88af 100644 --- a/docs/available-schema-types/available-schema-types.md +++ b/docs/available-schema-types/available-schema-types.md @@ -16,4 +16,4 @@ | Account | Specifies Hedera Account name | Custom Account | | Prefix | Specifies adding Units in front of quantity | Rs | | Postfix | Specifies adding Units after the quantity | Litres, Kgs,$ | - +| Enum | Specifies values to be added | Option 1, Option 2 | diff --git a/docs/demo-guide/demo-using-apis.md b/docs/demo-guide/demo-using-apis.md index f6ba2228d2..8892c0c8f8 100644 --- a/docs/demo-guide/demo-using-apis.md +++ b/docs/demo-guide/demo-using-apis.md @@ -28,7 +28,7 @@ ### 1.2 In the policy config there is a root block which is the top of the structure -![](../.gitbook/assets/API\_1.png) +![](<../.gitbook/assets/API\_1 (1).png>) ### 1.3 Request the config for the root block @@ -70,7 +70,7 @@ {% endswagger-response %} {% endswagger %} -![](<../.gitbook/assets/API\_2 (1).png>) +![](<../.gitbook/assets/API\_2 (1) (1).png>) ### 1.5 At present only PolicyRolesBlock is available to the user. Select the "INSTALLER" role. @@ -235,7 +235,7 @@ Years of registration {% endswagger-parameter %} {% endswagger %} -![](<../.gitbook/assets/API\_4 (1).png>) +![](<../.gitbook/assets/API\_4 (1) (1).png>) ### 1.8 Request the root block and all contained blocks again. @@ -297,7 +297,7 @@ Years of registration {% endswagger-response %} {% endswagger %} -![](../.gitbook/assets/API\_5.png) +![](<../.gitbook/assets/image 1.png>) ## 2. Login as a Standard Registry @@ -699,9 +699,9 @@ capacity {% endswagger-parameter %} {% endswagger %} -![](../.gitbook/assets/API\_9.png) +![](../.gitbook/assets/Sensor.png) -
+
### 3.4 Refresh the Blocks @@ -717,7 +717,7 @@ record in the grid (data[0]) {% endswagger-parameter %} {% endswagger %} -![](../.gitbook/assets/API\_11.png) +![](<../.gitbook/assets/image 4.png>) ### 3.6 Sample MRV Sender Data diff --git a/docs/demo-guide/irec-demo-guide.md b/docs/demo-guide/irec-demo-guide.md index d888f79fb2..166e4e05f9 100644 --- a/docs/demo-guide/irec-demo-guide.md +++ b/docs/demo-guide/irec-demo-guide.md @@ -19,9 +19,9 @@ This folder contains a sample file that is referenced in the Demo Guide ``` iREC 1 : 1661166202.802071003 -iREC 2 : 1661167347.064745885 -iREC 3 : 1661372984.332898003 -iREC 4 : 1661373147.975461003 +iREC 2 : 1662640724.951854568 +iREC 3 : 1662641840.731000003 +iREC 4 : 1662642008.325450377 ``` ![](../.gitbook/assets/iREC3\_new\_1.png) diff --git a/docs/getting-started/built-with.md b/docs/getting-started/built-with.md index 82ec350029..ddccc57bb8 100644 --- a/docs/getting-started/built-with.md +++ b/docs/getting-started/built-with.md @@ -8,8 +8,11 @@ The Guardian solution is built with the following major frameworks/libraries. * [MongoDB](https://www.mongodb.com/) * [Express](https://expressjs.com/) * [Nats](https://nats.io/) -* [TypeORM](https://typeorm.io/) +* [MikroORM](https://mikro-orm.io/) * [W3C VC-JS-HTTP](https://w3c.github.io/vc-data-model/) +* [Transmute](https://github.com/transmute-industries/verifiable-data/tree/main/packages/vc.js) +* [MathJS](https://mathjs.org) +* [Web3.Storage](https://github.com/web3-storage/web3.storage) **Frontend** diff --git a/docs/getting-started/getting-started/how-to-generate-web3.storage-api-key.md b/docs/getting-started/getting-started/how-to-generate-web3.storage-api-key.md index 0192cf51dc..932993e9bc 100644 --- a/docs/getting-started/getting-started/how-to-generate-web3.storage-api-key.md +++ b/docs/getting-started/getting-started/how-to-generate-web3.storage-api-key.md @@ -10,7 +10,7 @@ Following are the steps to follow to generate Web3.Storage API Key: 2\. Once Logged in successfully, hover over **Account** and click **Create an API Token** in the dropdown menu. -
+
3\. Now, enter a descriptive name for your API token and click **Create**. diff --git a/docs/getting-started/getting-started/installation.md b/docs/getting-started/getting-started/installation.md index ab3950836d..6df08cf523 100644 --- a/docs/getting-started/getting-started/installation.md +++ b/docs/getting-started/getting-started/installation.md @@ -43,6 +43,18 @@ or in `ipfs-client/.env.docker`: IPFS_STORAGE_API_KEY="" ``` +in `worker-service/ .env`: + +``` +IPFS_STORAGE_API_KEY="" +``` + +or in `worker-service/ .env.docker`: + +``` +IPFS_STORAGE_API_KEY="" +``` + 4\. Build and launch with Docker. Please note that this build is meant to be used in production and will not contain any debug information. From the project's root folder: ``` diff --git a/docs/multi-user-roles/roles-and-groups.md b/docs/multi-user-roles/roles-and-groups.md index a2eacf8e70..44d983123d 100644 --- a/docs/multi-user-roles/roles-and-groups.md +++ b/docs/multi-user-roles/roles-and-groups.md @@ -10,7 +10,7 @@ Roles can be created by adding Role Property and its Value in Policy Configurato
-
+
#### 1.2 Usage diff --git a/docs/schema-flow/schema-demo.md b/docs/schema-flow/schema-demo.md index 73c4b826e7..41f036a77c 100644 --- a/docs/schema-flow/schema-demo.md +++ b/docs/schema-flow/schema-demo.md @@ -49,12 +49,12 @@ There are different types of Field Types: * Email * Image * Account -* Units of Measure +* Units of Measure + * Prefix + * Postfix +* Enum - * Prefix - * Postfix - - Each of the above field types can be marked as either Marked or optional by checking the Required Field checkbox. +Each of the above field types can be marked as either Marked or optional by checking the Required Field checkbox. {% hint style="info" %} **Note:** @@ -63,6 +63,17 @@ There are different types of Field Types: 2. If there are multiple fields of the ‘Account’ with the same name, then the value from the most immediate scope, i.e. from the current (‘child’) document is used. {% endhint %} +{% hint style="info" %} +**Note: Important points to be noted when "Enum" type is selected:** + +1. Enum values can be added by editing or by importing it from link or from file. +2. If we are importing files by URL. The response should be ({"enum": \["Option1", "Option2", "Option3"]}) or has same format such as importing file (Options separated by new line symbol). + +Example of URL which has correct format: [https://ipfs.io/ipfs/bafkreihgbx6fsqup4psfbzjcf57zjdbfwisbjbsqzvwlg4hgx5s5xyqwzm](https://ipfs.io/ipfs/bafkreihgbx6fsqup4psfbzjcf57zjdbfwisbjbsqzvwlg4hgx5s5xyqwzm) + +3\. If we put more than five options, it will be automatically loaded to IPFS. +{% endhint %} + Once the above details are added, click on the Create button. ![](<../.gitbook/assets/image (2) (1) (2).png>) @@ -85,7 +96,7 @@ We also have a filter, where by default, all the Schemas of Policies are shown. Policy Schema can also be created by clicking on the New button. -![](<../.gitbook/assets/image (21).png>) +![](<../.gitbook/assets/image (21) (1).png>) Once the New button is clicked, we get a dialog box that asks for the following information:\ 1\. Schema Name @@ -106,7 +117,7 @@ To import the Schema, click on the Import button. Once the Import button is clicked, we get two options: Import from file and Import from IPFS -![](<../.gitbook/assets/image (29).png>) +![](<../.gitbook/assets/image (29) (2).png>) Import from file: You can select the required Schema .schema file from your local machine. Sample iREC Schema (iREC Schema.zip) is provided in the link: [https://github.com/hashgraph/guardian/tree/main/Demo%20Artifacts](https://github.com/hashgraph/guardian/tree/main/Demo%20Artifacts) diff --git a/frontend/package.json b/frontend/package.json index 6040ea61c6..bda974aecd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,5 +57,5 @@ "test": "ng test", "watch": "ng build --watch --configuration development --output-path ../www-data" }, - "version": "2.4.2" + "version": "2.5.0-prerelease" } diff --git a/frontend/src/app/material.module.ts b/frontend/src/app/material.module.ts index 28d0db3e23..64cc6e7e1f 100644 --- a/frontend/src/app/material.module.ts +++ b/frontend/src/app/material.module.ts @@ -28,7 +28,8 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSortModule } from '@angular/material/sort'; import { MatChipsModule } from '@angular/material/chips'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import {MatButtonToggleModule} from '@angular/material/button-toggle'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { DragDropModule } from '@angular/cdk/drag-drop'; @NgModule({ declarations: [], @@ -58,7 +59,8 @@ import {MatButtonToggleModule} from '@angular/material/button-toggle'; MatTooltipModule, MatSlideToggleModule, MatPaginatorModule, - MatButtonToggleModule + MatButtonToggleModule, + DragDropModule ], exports: [ MatTabsModule, @@ -89,7 +91,8 @@ import {MatButtonToggleModule} from '@angular/material/button-toggle'; MatSortModule, MatChipsModule, MatAutocompleteModule, - MatButtonToggleModule + MatButtonToggleModule, + DragDropModule ] }) export class MaterialModule { } \ No newline at end of file diff --git a/frontend/src/app/policy-engine/helpers/document-path/document-path.component.css b/frontend/src/app/policy-engine/helpers/document-path/document-path.component.css index 81083318d5..0b395e0b5a 100644 --- a/frontend/src/app/policy-engine/helpers/document-path/document-path.component.css +++ b/frontend/src/app/policy-engine/helpers/document-path/document-path.component.css @@ -3,8 +3,16 @@ overflow: hidden; padding: 2px; display: grid; - grid-template-columns: 130px 8px 190px; + grid-template-columns: 130px 8px 1fr fit-content(24px); } .select { +} + +.input-end { + min-width: 0; +} + +.input-end-tooltip { + margin-left: 2px; } \ No newline at end of file diff --git a/frontend/src/app/policy-engine/helpers/document-path/document-path.component.html b/frontend/src/app/policy-engine/helpers/document-path/document-path.component.html index 05a0742945..0ed272f969 100644 --- a/frontend/src/app/policy-engine/helpers/document-path/document-path.component.html +++ b/frontend/src/app/policy-engine/helpers/document-path/document-path.component.html @@ -9,5 +9,6 @@
- + + more_horiz \ No newline at end of file diff --git a/frontend/src/app/policy-engine/helpers/document-path/document-path.component.ts b/frontend/src/app/policy-engine/helpers/document-path/document-path.component.ts index b28e0fa43c..550ce21eb5 100644 --- a/frontend/src/app/policy-engine/helpers/document-path/document-path.component.ts +++ b/frontend/src/app/policy-engine/helpers/document-path/document-path.component.ts @@ -10,6 +10,7 @@ import { Component, EventEmitter, Inject, Input, Output, SimpleChanges } from '@ }) export class DocumentPath { @Input('value') value!: string; + @Input('displayTooltip') displayTooltip!: boolean; @Output('valueChange') valueChange = new EventEmitter(); @Input('readonly') readonly!: boolean; diff --git a/frontend/src/app/policy-engine/helpers/events-overview/events-overview.ts b/frontend/src/app/policy-engine/helpers/events-overview/events-overview.ts index 68fa84c37e..80b4b67a64 100644 --- a/frontend/src/app/policy-engine/helpers/events-overview/events-overview.ts +++ b/frontend/src/app/policy-engine/helpers/events-overview/events-overview.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, EventEmitter, HostListener, Injectable, Input, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { PolicyBlockModel, PolicyEventModel, PolicyModel } from '../../policy-model'; +import { PolicyBlockModel, PolicyEventModel, PolicyModel } from '../../structures/policy-model'; import { RegisteredBlocks } from '../../registered-blocks'; import { TreeFlatOverview } from '../tree-flat-overview/tree-flat-overview'; @@ -41,11 +41,7 @@ export class EventsOverview { this.setContext(this.context); } if (changes.active) { - if (this.active !== 'None') { - this.render() - } else { - this.refreshCanvas(); - } + this.render(); } } @@ -68,7 +64,12 @@ export class EventsOverview { } public render(): void { - if (this.active === 'None' || !this._canvas || !this._context) { + if (!this._canvas || !this._context) { + return; + } + if (this.active === 'None') { + this.renderData([]); + this.renderLine(); return; } const boxCanvas = this.refreshCanvas(); diff --git a/frontend/src/app/policy-engine/helpers/select-block/select-block.component.css b/frontend/src/app/policy-engine/helpers/select-block/select-block.component.css new file mode 100644 index 0000000000..5621e6b2e3 --- /dev/null +++ b/frontend/src/app/policy-engine/helpers/select-block/select-block.component.css @@ -0,0 +1,17 @@ +.block-name { + padding-left: 32px; + position: relative; +} + +.block-icon { + position: absolute; + left: 0px; + top: 0px; +} + +.custom { + height: 14px; + line-height: 14px; + display: inline-block; + min-width: 1px; +} \ No newline at end of file diff --git a/frontend/src/app/policy-engine/helpers/select-block/select-block.component.html b/frontend/src/app/policy-engine/helpers/select-block/select-block.component.html new file mode 100644 index 0000000000..05832ee23b --- /dev/null +++ b/frontend/src/app/policy-engine/helpers/select-block/select-block.component.html @@ -0,0 +1,13 @@ + + + {{value}} + + +
+
+ {{item.icon}} +
+ {{item.name}} +
+
+
\ No newline at end of file diff --git a/frontend/src/app/policy-engine/helpers/select-block/select-block.component.ts b/frontend/src/app/policy-engine/helpers/select-block/select-block.component.ts new file mode 100644 index 0000000000..888c244b79 --- /dev/null +++ b/frontend/src/app/policy-engine/helpers/select-block/select-block.component.ts @@ -0,0 +1,32 @@ +import { Component, EventEmitter, Inject, Input, Output, SimpleChanges } from '@angular/core'; + +/** + * SelectBlock. + */ +@Component({ + selector: 'select-block', + templateUrl: './select-block.component.html', + styleUrls: ['./select-block.component.css'] +}) +export class SelectBlock { + @Input('blocks') allBlocks!: any[]; + @Input('readonly') readonly!: boolean; + + @Input('value') value!: any; + @Output('valueChange') valueChange = new EventEmitter(); + + data?:any[]; + + constructor() { + } + + onChange() { + this.valueChange.emit(this.value); + } + + ngOnChanges(changes: SimpleChanges) { + setTimeout(() => { + this.data = this.allBlocks; + }, 0); + } +} \ No newline at end of file diff --git a/frontend/src/app/policy-engine/helpers/tree-flat-overview/tree-flat-overview.ts b/frontend/src/app/policy-engine/helpers/tree-flat-overview/tree-flat-overview.ts index 1f835497f4..8bc9c099c4 100644 --- a/frontend/src/app/policy-engine/helpers/tree-flat-overview/tree-flat-overview.ts +++ b/frontend/src/app/policy-engine/helpers/tree-flat-overview/tree-flat-overview.ts @@ -54,6 +54,7 @@ export class TreeFlatOverview { expandDelay = 1000; validateDrop = false; isCollapseAll = true; + eventsDisabled = false; public readonly context: ElementRef; @@ -68,7 +69,9 @@ export class TreeFlatOverview { this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); this.treeControl.expansionModel.changed.subscribe(e => { this.isCollapseAll = !e.source.selected.length; - this.change.emit(); + if (!this.eventsDisabled) { + this.change.emit(); + } }) } @@ -179,7 +182,9 @@ export class TreeFlatOverview { // rebuild tree with mutated data // this.rebuildTreeForData(changedData); this.reorder.emit(changedData); - this.change.emit(); + if (!this.eventsDisabled) { + this.change.emit(); + } } setErrors(errors: any) { @@ -259,6 +264,7 @@ export class TreeFlatOverview { */ rebuildTreeForData(data: BlockNode[]) { + this.eventsDisabled = true; this.blocks = data; this.root = data[0]; this.dataSource.data = data; @@ -277,7 +283,10 @@ export class TreeFlatOverview { } else { this.currentBlock = this.root; } - + setTimeout(() => { + this.eventsDisabled = false; + this.change.emit(); + }, 100); } getName(node: FlatBlockNode) { @@ -297,7 +306,9 @@ export class TreeFlatOverview { event.stopPropagation(); this.currentBlock = node.node; this.select.emit(this.currentBlock); - this.change.emit(); + if (!this.eventsDisabled) { + this.change.emit(); + }; return false; } @@ -305,7 +316,9 @@ export class TreeFlatOverview { event.preventDefault(); event.stopPropagation(); this.delete.emit(block.node); - this.change.emit(); + if (!this.eventsDisabled) { + this.change.emit(); + }; return false; } diff --git a/frontend/src/app/policy-engine/policies/policies.component.css b/frontend/src/app/policy-engine/policies/policies.component.css index a7c1d051b9..4a51e3c6b6 100644 --- a/frontend/src/app/policy-engine/policies/policies.component.css +++ b/frontend/src/app/policy-engine/policies/policies.component.css @@ -147,6 +147,13 @@ border-right: 1px solid #dddddd; } +.table-container .mat-column-tokens { + padding-left: 8px; + width: 95px; + max-width: 95px; + border-right: 1px solid #dddddd; +} + .table-container .mat-column-schemas { padding-left: 8px; width: 95px; diff --git a/frontend/src/app/policy-engine/policies/policies.component.html b/frontend/src/app/policy-engine/policies/policies.component.html index 41eb983242..79b556bd2c 100644 --- a/frontend/src/app/policy-engine/policies/policies.component.html +++ b/frontend/src/app/policy-engine/policies/policies.component.html @@ -28,10 +28,19 @@ {{element.topicId}} + + Tokens + + + Tokens + + + Schemas - Schemas + + Schemas diff --git a/frontend/src/app/policy-engine/policies/policies.component.ts b/frontend/src/app/policy-engine/policies/policies.component.ts index 749c02d5bc..3e7e921d4d 100644 --- a/frontend/src/app/policy-engine/policies/policies.component.ts +++ b/frontend/src/app/policy-engine/policies/policies.component.ts @@ -95,6 +95,7 @@ export class PoliciesComponent implements OnInit, OnDestroy { 'roles', 'topic', 'version', + 'tokens', 'schemas', 'operation', 'open', diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.html index 8efcd03a36..f6d2870f6e 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.html @@ -41,7 +41,7 @@ {{field.title}} - + @@ -78,7 +78,7 @@ {{field.title}} - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.ts index c0130ef3c4..685cd181df 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-config/calculate-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, SchemaField, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'policyRolesBlock' type. @@ -8,10 +8,8 @@ import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-mode @Component({ selector: 'calculate-config', templateUrl: './calculate-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './calculate-config.component.css' - ] + styleUrls: ['./calculate-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class CalculateConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.html index 3943c24a80..be43a7ea15 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.html @@ -33,14 +33,14 @@ Variable - + Formula - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.ts index 3bed40c81f..48d5a02c49 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/calculate-math-config/calculate-math-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'policyRolesBlock' type. @@ -8,10 +8,8 @@ import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-mode @Component({ selector: 'calculate-math-config', templateUrl: './calculate-math-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './calculate-math-config.component.css' - ] + styleUrls: ['./calculate-math-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class CalculateMathConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -56,7 +54,7 @@ export class CalculateMathConfigComponent implements OnInit { }) } - onRemoveEquation(i:number) { + onRemoveEquation(i: number) { this.block.equations.splice(i, 1); } } diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.html index f53a13c0c2..e5036357ed 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.html @@ -14,11 +14,37 @@ + + + Document Signer + + + Policy Owner + First Document Owner + First Document Issuer + + + + + + + Id Type + + + + DID (New DID) + UUID (New UUID) + Owner (Owner DID) + From First Document Id + + + + Expression - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.ts index 7f81b5d8c8..304f71a7c0 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component.ts @@ -1,17 +1,15 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; import { MatDialog } from '@angular/material/dialog'; import { CodeEditorDialogComponent } from '../../../../helpers/code-editor-dialog/code-editor-dialog.component'; import { Schema, Token, SchemaField } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; @Component({ - selector: 'app-custom-logic-config', - templateUrl: './custom-logic-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './custom-logic-config.component.css' - ] + selector: 'app-custom-logic-config', + templateUrl: './custom-logic-config.component.html', + styleUrls: ['./custom-logic-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class CustomLogicConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -40,6 +38,8 @@ export class CustomLogicConfigComponent implements OnInit { this.block = block.properties; this.block.uiMetaData = this.block.uiMetaData || {} this.block.expression = this.block.expression || '' + this.block.documentSigner = this.block.documentSigner || ''; + this.block.idType = this.block.idType || ''; } editExpression($event: MouseEvent) { diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.html index 233352a047..c9f41a4e79 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.html @@ -24,7 +24,7 @@ Empty Data - + @@ -66,7 +66,7 @@ Variable Name - + Variable Value - + @@ -83,7 +83,7 @@ Condition - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.ts index b3f06796fe..d603f2203d 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/aggregate-config/aggregate-config.component.ts @@ -1,9 +1,9 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; import { MatDialog } from '@angular/material/dialog'; import { CronConfigDialog } from '../../../../helpers/cron-config-dialog/cron-config-dialog.component'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'aggregateDocument' type. @@ -11,10 +11,8 @@ import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-mode @Component({ selector: 'aggregate-config', templateUrl: './aggregate-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './aggregate-config.component.css' - ] + styleUrls: ['./aggregate-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class AggregateConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.html index 79d479f407..5191be04fe 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.html @@ -29,28 +29,28 @@ Check Owned by User - + Check Owned by Group - + Check Assigned to User - + Check Assigned to Group - + @@ -99,14 +99,14 @@ Field - + Value - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.ts index 9779b6ea58..1c012b4320 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-validator-config/document-validator-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'document-validator-config', templateUrl: './document-validator-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './document-validator-config.component.css' - ] + styleUrls: ['./document-validator-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class DocumentValidatorConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.css b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.css index e69de29bb2..5621e6b2e3 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.css +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.css @@ -0,0 +1,17 @@ +.block-name { + padding-left: 32px; + position: relative; +} + +.block-icon { + position: absolute; + left: 0px; + top: 0px; +} + +.custom { + height: 14px; + line-height: 14px; + display: inline-block; + min-width: 1px; +} \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.html index 3fc7618b1c..1a0a7afa4b 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.html @@ -11,7 +11,14 @@ Title - + + + + + + Enable common sorting + + @@ -43,7 +50,7 @@ Field {{i}} - + {{field.name}} @@ -60,6 +67,7 @@ TEXT + Issuer BUTTON BLOCK @@ -69,35 +77,35 @@ Title - + Tooltip - + Cell Content - + UI Class - + width - + @@ -106,19 +114,7 @@ add_link Bind Group - - - {{field?.bindGroup}} - - -
-
- {{item.icon}} -
- {{item.name}} -
-
-
+ @@ -147,21 +143,8 @@ Bind Block - - - - {{field?.bindBlock}} - - - -
-
- {{item.icon}} -
- {{item.name}} -
-
-
+ +
@@ -170,21 +153,21 @@ Dialog Type - + Dialog Content - + Dialog Class - + @@ -196,21 +179,8 @@ Bind Block - - - - {{field?.bindBlock}} - - - -
-
- {{item.icon}} -
- {{item.name}} -
-
-
+ + @@ -225,19 +195,7 @@ Bind Block - - - {{field?.bindBlock}} - - -
-
- {{item.icon}} -
- {{item.name}} -
-
-
+ diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.ts index 0342f29405..4a0b4a7f01 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/document-viewer-config/document-viewer-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { RegisteredBlocks } from 'src/app/policy-engine/registered-blocks'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; @@ -10,10 +10,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'document-viewer-config', templateUrl: './document-viewer-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './document-viewer-config.component.css' - ] + styleUrls: ['./document-viewer-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class DocumentSourceComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.html index d9cfacca2a..f55b5f277d 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.html @@ -4,7 +4,7 @@ Entity Type - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.ts index 303a5eb80f..66d187691b 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/external-data-config/external-data-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'external-data-config', templateUrl: './external-data-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './external-data-config.component.css' - ] + styleUrls: ['./external-data-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class ExternalDataConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.html index 4adb139681..d7b5608cfe 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.html @@ -13,14 +13,14 @@ Can Be Empty - + Field - + @@ -38,14 +38,14 @@ Option Name - + Option Value - + @@ -61,14 +61,14 @@ Title - + Button Content - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.ts index 2e5c89180d..379cbecae4 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/filters-addon-config/filters-addon-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'filters-addon-config', templateUrl: './filters-addon-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './filters-addon-config.component.css' - ] + styleUrls: ['./filters-addon-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class FiltersAddonConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/reassigning-config/reassigning-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/reassigning-config/reassigning-config.component.ts index 869afb3b82..4143928fc4 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/reassigning-config/reassigning-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/reassigning-config/reassigning-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'reassigning-config', templateUrl: './reassigning-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './reassigning-config.component.css' - ] + styleUrls: ['./reassigning-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class ReassigningConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.html index 3c77eeaa4f..51e935e891 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.html @@ -31,7 +31,7 @@ Preset - + @@ -59,7 +59,7 @@ Title - + @@ -67,7 +67,7 @@ Description - + @@ -77,42 +77,42 @@ Title - + Button Content - + Button Class - + Dialog Title - + Dialog Description - + Dialog Class - + @@ -179,7 +179,7 @@ Readonly - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.ts index 172f7807eb..04de6fd4ba 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/request-config/request-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'request-config', templateUrl: './request-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './request-config.component.css' - ] + styleUrls: ['./request-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class RequestConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -103,12 +101,12 @@ export class RequestConfigComponent implements OnInit { } } - const dMap:any = {}; + const dMap: any = {}; for (let i = 0; i < this.presetMap.length; i++) { const f = this.presetMap[i]; dMap[f.title] = f.name; } - for (let i = 0; i Update previous document status - + Status value - + \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/revoke-config/revoke-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/revoke-config/revoke-config.component.ts index 1a23ec1dbf..28a1b7be69 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/revoke-config/revoke-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/revoke-config/revoke-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'revoke-config', templateUrl: './revoke-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './revoke-config.component.css' - ] + styleUrls: ['./revoke-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class RevokeConfigComponent implements OnInit { @Input('all') all!: BlockNode[]; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.css b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.css index e69de29bb2..1aecb80c1d 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.css +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.css @@ -0,0 +1,10 @@ +.mat-option-link { + color: #6166ff; +} + +.mat-option-link mat-icon { + margin-right: 2px; + top: -2px; + left: -4px; + position: relative; +} \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.html index 7cf6ec6d72..82912a7c27 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.html @@ -76,14 +76,14 @@ Entity Type - + Force New Document - + @@ -91,7 +91,7 @@ Memo - + @@ -117,7 +117,7 @@ Option {{i}} - + {{option.name}} : {{option.value}} @@ -129,14 +129,14 @@ Option Name - + Option Value - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.ts index 454a33c09c..f8967f4ffd 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/send-config/send-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'send-config', templateUrl: './send-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './send-config.component.css' - ] + styleUrls: ['./send-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class SendConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.html index 7736d93a64..f507feedcf 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.html @@ -30,28 +30,28 @@ Owned by User - + Owned by Group - + Assigned to User - + Assigned to Group - + @@ -59,14 +59,14 @@ View History - + Order Field - + @@ -104,7 +104,7 @@ Field {{i}} - + {{field.field}} {{field.type}} {{field.value}} @@ -125,14 +125,14 @@ Field - + Value - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.ts index f459518e51..51a3af8d9c 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/source-addon-config/source-addon-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'source-addon-config', templateUrl: './source-addon-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './source-addon-config.component.css' - ] + styleUrls: ['./source-addon-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class SourceAddonConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/timer-config/timer-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/timer-config/timer-config.component.ts index e911ef6680..a36c6a3eb2 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/documents/timer-config/timer-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/documents/timer-config/timer-config.component.ts @@ -1,9 +1,9 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; import { MatDialog } from '@angular/material/dialog'; import { CronConfigDialog } from '../../../../helpers/cron-config-dialog/cron-config-dialog.component'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'timer' type. @@ -11,10 +11,8 @@ import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-mode @Component({ selector: 'timer-config', templateUrl: './timer-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './timer-config.component.css' - ] + styleUrls: ['./timer-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class TimerConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -26,7 +24,7 @@ export class TimerConfigComponent implements OnInit { propHidden: any = { main: false, - options:false, + options: false, expressionsGroup: false, expressions: {}, }; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.html index f0d7bab149..ab75114258 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.html @@ -23,7 +23,7 @@ Title - + @@ -32,7 +32,7 @@ Field - + @@ -62,7 +62,7 @@ Option Tag - + Name - + Value - + UI Class - + @@ -108,7 +108,7 @@ Button Content - + @@ -124,21 +124,21 @@ Option Name - + Option Value - + Field - + @@ -148,7 +148,7 @@ Button Content - + @@ -176,7 +176,7 @@ Target Url - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.ts index b1e5fbe742..bf5caf84d8 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/action-config/action-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token, UserType } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { RegisteredBlocks } from 'src/app/policy-engine/registered-blocks'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; @@ -10,10 +10,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'action-config', templateUrl: './action-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './action-config.component.css' - ] + styleUrls: ['./action-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class ActionConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.html index 55c24311d0..fde2f5c57a 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.html @@ -20,7 +20,7 @@ expand_more Button {{i}} - + {{button.name}} delete @@ -44,7 +44,7 @@ Button Tag - + @@ -53,7 +53,7 @@ Dialog Title - + Dialog Description - + @@ -70,14 +70,14 @@ Button Name - + Field - + Value - + UI Class - + @@ -122,7 +122,7 @@ Filter {{j}} - + {{filter.field}} {{filter.type}} {{filter.value}} @@ -146,14 +146,14 @@ Field - + Value - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.ts index e495c3d384..feae6c53f2 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/button-config/button-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token, UserType } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { RegisteredBlocks } from 'src/app/policy-engine/registered-blocks'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; @@ -10,10 +10,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'button-config', templateUrl: './button-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './button-config.component.css' - ] + styleUrls: ['./button-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class ButtonConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -62,7 +60,7 @@ export class ButtonConfigComponent implements OnInit { type: 'selector', filters: [] }) - this.propHidden.buttons[this.block.uiMetaData.buttons.length-1] = {}; + this.propHidden.buttons[this.block.uiMetaData.buttons.length - 1] = {}; } getIcon(block: any) { diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.html index f81dfb617b..8bdf7ef45c 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.html @@ -4,7 +4,7 @@ Cyclic - + @@ -19,7 +19,7 @@ Title - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.ts index 6b91bfcf15..383a65b41b 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/container-config/container-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** @@ -9,10 +9,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'container-config', templateUrl: './container-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './container-config.component.css' - ] + styleUrls: ['./container-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class ContainerConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -43,7 +41,7 @@ export class ContainerConfigComponent implements OnInit { load(block: PolicyBlockModel) { this.block = block.properties; this.block.uiMetaData = this.block.uiMetaData || {} - this.block.uiMetaData.type = this.block.uiMetaData.type || 'blank'; + this.block.uiMetaData.type = this.block.uiMetaData.type || 'blank'; } onHide(item: any, prop: any) { diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/group-manager-config/group-manager-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/main/group-manager-config/group-manager-config.component.ts index 97404d1ce9..6e42b4dcdd 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/group-manager-config/group-manager-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/group-manager-config/group-manager-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'policyRolesBlock' type. @@ -8,10 +8,8 @@ import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-mode @Component({ selector: 'group-manager-config', templateUrl: './group-manager-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './group-manager-config.component.css' - ] + styleUrls: ['./group-manager-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class GroupManagerConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.html index 0f438d4ee9..0403cde647 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.html @@ -11,14 +11,14 @@ Title - + Description - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.ts index 57cbf765ac..1351e132f2 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/information-config/information-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { RegisteredBlocks } from 'src/app/policy-engine/registered-blocks'; import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; @@ -10,10 +10,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'information-config', templateUrl: './information-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './information-config.component.css' - ] + styleUrls: ['./information-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class InformationConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.html index c1b7d40f86..0a1a5df8da 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.html @@ -29,14 +29,14 @@ Title - + Description - + \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.ts index 2a7b43131e..12e165a909 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/roles-config/roles-config.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'policyRolesBlock' type. @@ -8,10 +8,8 @@ import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-mode @Component({ selector: 'roles-config', templateUrl: './roles-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './roles-config.component.css' - ] + styleUrls: ['./roles-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class RolesConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/main/switch-config/switch-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/main/switch-config/switch-config.component.html index fa87aa3b45..7700fb60dd 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/main/switch-config/switch-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/main/switch-config/switch-config.component.html @@ -57,7 +57,7 @@ Condition Tag - + Condition - + Title - + Description - + - + - Visible + Multiple - + @@ -45,8 +45,8 @@ Icon
- - +
@@ -68,6 +68,65 @@ + + + + expand_more + + Dynamic Filters + +
+ add + Add Dynamic Filter +
+ + + + + + + expand_more + + + Dynamic Filter {{i}} + + + {{block.dynamicFilters[i].nextItemField}} {{block.dynamicFilters[i].type}} {{block.dynamicFilters[i].field}} + + + delete + + + + + + + Type + + + Equal + Not Equal + In + Not In + + + + + + Field Path + + + + + + + Next Item Field Path + + + + + +
expand_more @@ -89,6 +148,9 @@ Filter {{i}} + + {{block.filters[i].field}} {{block.filters[i].type}} {{block.filters[i].value}} + delete @@ -121,14 +183,14 @@ Field - + Value - + @@ -164,14 +226,14 @@ Name - + Value - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/report/report-item-config/report-item-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/report/report-item-config/report-item-config.component.ts index 2e65c80759..47c1eac046 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/report/report-item-config/report-item-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/report/report-item-config/report-item-config.component.ts @@ -1,11 +1,10 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Schema, Token } from '@guardian/interfaces'; import { IconPreviewDialog } from 'src/app/components/icon-preview-dialog/icon-preview-dialog.component'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; import { API_IPFS_GATEWAY_URL } from 'src/app/services/api'; import { IPFSService } from 'src/app/services/ipfs.service'; -import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source'; /** * Settings for block of 'reportItemBlock' type. @@ -13,10 +12,8 @@ import { BlockNode } from '../../../../helpers/tree-data-source/tree-data-source @Component({ selector: 'report-item-config', templateUrl: './report-item-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './report-item-config.component.css' - ] + styleUrls: ['./report-item-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class ReportItemConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; @@ -33,7 +30,9 @@ export class ReportItemConfigComponent implements OnInit { filterGroup: false, filters: {}, variableGroup: false, - variables: {} + variables: {}, + dynamicFilterGroup: false, + dynamicFilters: {} }; block!: any; @@ -55,6 +54,7 @@ export class ReportItemConfigComponent implements OnInit { load(block: PolicyBlockModel) { this.block = block.properties; this.block.filters = this.block.filters || []; + this.block.dynamicFilters = this.block.dynamicFilters || []; this.block.variables = this.block.variables || []; this.block.visible = this.block.visible !== false; this.block.iconType = this.block.iconType; @@ -80,6 +80,14 @@ export class ReportItemConfigComponent implements OnInit { this.block.filters.splice(i, 1); } + addDynamicFilter() { + this.block.dynamicFilters.push({}); + } + + onRemoveDynamicFilter(i: number) { + this.block.dynamicFilters.splice(i, 1); + } + onFileSelected(event: any, block: any) { const file = event?.target?.files[0]; @@ -104,4 +112,8 @@ export class ReportItemConfigComponent implements OnInit { } }); } + + onMultipleChange() { + this.block.dynamicFilters = []; + } } diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.html index 5dcbcbaa33..0a3a35c117 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.html @@ -20,7 +20,7 @@ Rule - + @@ -37,7 +37,7 @@ Account Id (Field) - + @@ -45,7 +45,7 @@ Memo **********.********* - + \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.ts index 71fd9cae3a..ba5ea38c01 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/mint-config/mint-config.component.ts @@ -1,16 +1,14 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'mintDocument' and 'wipeDocument' types. */ @Component({ selector: 'mint-config', templateUrl: './mint-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './mint-config.component.css' - ] + styleUrls: ['./mint-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class MintConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.html index aa79199c83..d85c252da1 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.html @@ -22,7 +22,7 @@ Account Id (Field) - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.ts index 2c355d83b9..9e8f475826 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-action-config/token-action-config.component.ts @@ -1,16 +1,14 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'mintDocument' and 'wipeDocument' types. */ @Component({ selector: 'token-action-config', templateUrl: './token-action-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './token-action-config.component.css' - ] + styleUrls: ['./token-action-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class TokenActionConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.html b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.html index 74e25a4bcb..9a8b4090e1 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.html @@ -22,7 +22,7 @@ Account Id (Field) - + diff --git a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.ts b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.ts index ba85bb2fa2..17bdb29a7c 100644 --- a/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/blocks/tokens/token-confirmation-config/token-confirmation-config.component.ts @@ -1,16 +1,14 @@ -import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Schema, Token } from '@guardian/interfaces'; -import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/policy-model'; +import { PolicyBlockModel, PolicyModel } from 'src/app/policy-engine/structures/policy-model'; /** * Settings for block of 'mintDocument' and 'wipeDocument' types. */ @Component({ selector: 'token-confirmation-config', templateUrl: './token-confirmation-config.component.html', - styleUrls: [ - './../../../common-properties/common-properties.component.css', - './token-confirmation-config.component.css' - ] + styleUrls: ['./token-confirmation-config.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class TokenConfirmationConfigComponent implements OnInit { @Input('policy') policy!: PolicyModel; diff --git a/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.css b/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.css index 207ec63b7b..0cc0dd70ca 100644 --- a/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.css +++ b/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.css @@ -1,59 +1,43 @@ -.label { - display: flex; -} - -.label-header { - padding: 5px; - font-weight: 500; -} - -.label-value { - padding: 5px; -} - -.grid-field { - display: flex; - height: 50px; - margin-bottom: 5px; - margin-left: 25px; +.grid-setting { + position: relative; + height: 100%; + width: 100%; + overflow: auto; } - -.grid-field-label { - height: 50px; - margin-right: 8px; - align-content: center; +.grid-setting::ng-deep .loading { + background: rgb(255 255 255 / 70%); + position: absolute; + z-index: 99; + top: 0; + left: 0; + bottom: 0; + right: 0; display: flex; align-items: center; - min-width: 30px; + justify-items: center; + justify-content: center; + align-content: center; } -.grid-field-label.field-name { - min-width: 202px; +.grid-setting::ng-deep .label { + display: flex; } -.add-btn { +.grid-setting::ng-deep .add-btn { color: #6166ff; margin-bottom: 5px; position: relative; user-select: none; } -.add-btn span { +.grid-setting::ng-deep .add-btn span { position: absolute; left: 40px; top: 1px } -.grid-setting { - position: relative; - height: 100%; - width: 100%; - overflow: auto; -} - - -.table { +.grid-setting::ng-deep .table { border: 1px solid #3f51b5; margin: 5px; display: inline-flex; @@ -64,12 +48,12 @@ background: #3f51b5; } -.table-body { +.grid-setting::ng-deep .table-body { overflow-y: scroll; max-height: 100%; } -.header { +.grid-setting::ng-deep .header { width: 480px; margin: 0; background: #3f51b5; @@ -78,29 +62,29 @@ top: 0; } -.header .propHeader { +.grid-setting::ng-deep .header .propHeader { background: #3f51b5; color: #fff; position: relative; } -.header .propHeader .propHeaderCell { +.grid-setting::ng-deep .header .propHeader .propHeaderCell { font-weight: 500; } -.header .propRowCol { +.grid-setting::ng-deep .header .propRowCol { background: #3f51b5; width: 18px; position: relative; } -.properties { +.grid-setting::ng-deep .properties { width: 530px; margin: 0px; background: #e5e5e5; } -.propHeader { +.grid-setting::ng-deep .propHeader { background: #e5e5e5; color: black; position: relative; @@ -108,8 +92,8 @@ position: relative; } -.properties .propHeader::after, -.table-body .propHeader::after { +.grid-setting::ng-deep .properties .propHeader::after, +.grid-setting::ng-deep .table-body .propHeader::after { position: absolute; content: ''; height: 2px; @@ -119,7 +103,7 @@ bottom: -1px; } -.propBottom { +.grid-setting::ng-deep .propBottom { background: #fff; color: black; font-weight: 500; @@ -134,49 +118,49 @@ box-shadow: 0px -1px 0 0 #919191; } -.propRow .propRowCell { +.grid-setting::ng-deep .propRow .propRowCell { background: #fff; position: relative; } -.propRow:nth-child(2n) .propRowCell { +.grid-setting::ng-deep .propRow:nth-child(2n) .propRowCell { background: #f7f7f7; } -.cellName { +.grid-setting::ng-deep .cellName { width: 160px; padding-left: 5px; } -.table-body .cellName { +.grid-setting::ng-deep .table-body .cellName { height: 24px; } -.subRow .cellName { +.grid-setting::ng-deep .subRow .cellName { padding-left: 20px; width: 145px; } -.subRow-2 .cellName { +.grid-setting::ng-deep .subRow-2 .cellName { padding-left: 40px; width: 125px; } -.subRow-3 .cellName { +.grid-setting::ng-deep .subRow-3 .cellName { padding-left: 60px; width: 105px; } -.subRow-4 .cellName { +.grid-setting::ng-deep .subRow-4 .cellName { padding-left: 80px; width: 85px; } -.cellTitle { +.grid-setting::ng-deep .cellTitle { font-weight: 500; } -.propRowCol { +.grid-setting::ng-deep .propRowCol { background: #e5e5e5; width: 18px; position: relative; @@ -184,7 +168,7 @@ user-select: none; } -.propRowCol mat-icon { +.grid-setting::ng-deep .propRowCol mat-icon { position: absolute; left: -1px; top: -1px; @@ -193,55 +177,20 @@ pointer-events: none; } -.propRowCol[collapse="true"] mat-icon { +.grid-setting::ng-deep .propRowCol[collapse="true"] mat-icon { transform: rotate(-90deg); top: 2px; } -.propRow[collapse="true"] { +.grid-setting::ng-deep .propRow[collapse="true"] { display: none; } -.ui-properties { - width: 478px; - margin: 0px 5px; - background: #e5e5e5; - border: 1px solid #3f51b5; - border-top: none; -} - -.ui-properties .ui-prop-row { - display: flex; -} - -.ui-properties .ui-prop-row-col { - height: 22px; - position: relative; - width: 24px; -} - -.ui-properties .ui-prop-row-header { - height: 18px; - background: #e5e5e5; - color: black; - position: relative; - font-weight: 500; - padding: 2px 5px; -} - -.ui-properties .ui-prop-row-body { - width: 100%; - min-height: 60px; - background: #fff; - border-left: 24px solid #e5e5e5; - position: relative; - display: flex; - flex-direction: column; - overflow: hidden; +.grid-setting::ng-deep .propHeader[collapse="true"] { + display: none; } - -.propAdd { +.grid-setting::ng-deep .propAdd { height: 20px; width: 250px; overflow: hidden; @@ -254,7 +203,7 @@ user-select: none; } -.propAdd mat-icon { +.grid-setting::ng-deep .propAdd mat-icon { position: absolute; left: 1px; top: -1px; @@ -262,15 +211,15 @@ pointer-events: none; } -.propAdd span { +.grid-setting::ng-deep .propAdd span { pointer-events: none; } -.propAdd:hover { +.grid-setting::ng-deep .propAdd:hover { text-decoration: underline; } -.icon-event { +.grid-setting::ng-deep .icon-event { display: inline-block; overflow: visible; font-size: 18px; @@ -282,7 +231,7 @@ top: 4px; } -.icon-link { +.grid-setting::ng-deep .icon-link { display: inline-block; overflow: visible; font-size: 18px; @@ -295,7 +244,7 @@ user-select: none; } -.cellName span { +.grid-setting::ng-deep .cellName span { width: 150px; overflow: hidden; white-space: nowrap; @@ -303,7 +252,7 @@ display: block; } -.cellValue span { +.grid-setting::ng-deep .cellValue span { width: 300px; overflow: hidden; white-space: nowrap; @@ -311,36 +260,41 @@ display: block; } -.properties input { +.grid-setting::ng-deep .properties .prop-input { width: 325px; } -.properties input[type="checkbox"] { +.grid-setting::ng-deep .properties input[type="checkbox"] { width: auto; } -input[readonly] { +.grid-setting::ng-deep input[readonly] { border-color: transparent; background: transparent; outline: none; } -input[readonly][type="checkbox"] { +.grid-setting::ng-deep input[readonly][type="checkbox"] { pointer-events: none; filter: grayscale(1); } -*[readonly="true"] .propAdd { +.grid-setting::ng-deep *[readonly="true"] .propAdd { pointer-events: none; filter: grayscale(1); } -.readonly-prop { +.grid-setting::ng-deep .readonly-prop { padding-left: 2px; - color: rgba(0,0,0,.45); + color: rgba(0, 0, 0, .45); + max-width: 328px; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } -.remove-prop { +.grid-setting::ng-deep .remove-prop { position: absolute; top: 2px; right: 0; @@ -348,57 +302,45 @@ input[readonly][type="checkbox"] { cursor: pointer; } -.remove-prop mat-icon { +.grid-setting::ng-deep .remove-prop mat-icon { font-size: 20px; } -.remove-prop[readonly="true"] { +.grid-setting::ng-deep .remove-prop[readonly="true"] { display: none; } -.lvl[lvl="1"] { +.grid-setting::ng-deep .lvl[lvl="1"] { padding-left: 0px !important; width: 140px !important; } -.lvl[lvl="1"] { +.grid-setting::ng-deep .lvl[lvl="1"] { padding-left: 8px !important; width: 132px !important; } -.lvl[lvl="2"] { +.grid-setting::ng-deep .lvl[lvl="2"] { padding-left: 16px !important; width: 124px !important; } -.lvl[lvl="3"] { +.grid-setting::ng-deep .lvl[lvl="3"] { padding-left: 24px !important; width: 116px !important; } -.lvl[lvl="3"] { +.grid-setting::ng-deep .lvl[lvl="3"] { padding-left: 32px !important; width: 108px !important; } -.lvl[lvl="4"] { +.grid-setting::ng-deep .lvl[lvl="4"] { padding-left: 40px !important; width: 100px !important; } -.mat-option-link { - - color: #6166ff; -} - -.mat-option-link mat-icon { - margin-right: 2px; - top: -2px; - left: -4px; - position: relative; -} - -.prop-icon { +.grid-setting::ng-deep .prop-icon { display: inline-block !important; height: 18px !important; width: auto !important; @@ -406,7 +348,7 @@ input[readonly][type="checkbox"] { overflow: hidden !important; } -.prop-icon-text { +.grid-setting::ng-deep .prop-icon-text { display: inline-block !important; height: 16px !important; width: auto !important; @@ -414,36 +356,26 @@ input[readonly][type="checkbox"] { overflow: hidden !important; } -.prop-icon mat-icon { +.grid-setting::ng-deep .prop-icon mat-icon { position: relative !important; font-size: 20px !important; } -div[disabled="true"] { +.grid-setting::ng-deep div[disabled="true"] { filter: grayscale(1); opacity: 0.7; } -.prop-icon-event { +.grid-setting::ng-deep .prop-icon-event { color: #bd9012; width: 20px !important; } -.prop-icon-event[invalid="true"] { - color: #f00; -} - -.block-name { - padding-left: 32px; - position: relative; -} -.block-icon { - position: absolute; - left: 0px; - top: 0px; +.grid-setting::ng-deep .prop-icon-event[invalid="true"] { + color: #f00; } -.prop-icon-output-event { +.grid-setting::ng-deep .prop-icon-output-event { display: inline-block !important; height: 18px !important; width: auto !important; @@ -454,7 +386,7 @@ div[disabled="true"] { position: relative; } -.prop-icon-input-event { +.grid-setting::ng-deep .prop-icon-input-event { display: inline-block !important; height: 18px !important; width: auto !important; @@ -464,13 +396,69 @@ div[disabled="true"] { left: 1px; position: relative; } -.prop-icon-output-event mat-icon, -.prop-icon-input-event mat-icon { + +.grid-setting::ng-deep .prop-icon-output-event mat-icon, +.grid-setting::ng-deep .prop-icon-input-event mat-icon { position: relative !important; font-size: 18px !important; top: 0px; } +.grid-setting::ng-deep .text-collapse { + color: transparent; + user-select: none; +} + +.grid-setting::ng-deep .text-collapse[collapse="true"] { + color: inherit; + user-select: inherit; +} + +.grid-setting::ng-deep .common-properties-button { + width: 99%; + font-size: 12px; + line-height: 25px; + color: #6166ff; + background-color: white; +} + +.grid-setting::ng-deep .common-properties-button-container { + display: flex; + justify-content: center; + align-self: center; + padding: 3px; +} + +.grid-setting::ng-deep .custom-properties { + display: contents; +} + +.grid-setting::ng-deep *[offset="1"] .propRowCell.cellName { + padding-left: 20px; + width: 145px; +} + +.grid-setting::ng-deep *[offset="2"] .propRowCell.cellName { + padding-left: 40px; + width: 125px; +} + +.grid-setting::ng-deep *[offset="3"] .propRowCell.cellName { + padding-left: 60px; + width: 105px; +} + +.block-name { + padding-left: 32px; + position: relative; +} + +.block-icon { + position: absolute; + left: 0px; + top: 0px; +} + .custom { height: 14px; line-height: 14px; @@ -478,11 +466,13 @@ div[disabled="true"] { min-width: 1px; } -.text-collapse { - color: transparent; - user-select: none; +.mat-option-link { + color: #6166ff; } -.text-collapse[collapse="true"] { - color: inherit; - user-select: inherit; + +.mat-option-link mat-icon { + margin-right: 2px; + top: -2px; + left: -4px; + position: relative; } \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.html b/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.html index dd725132e1..08d5b10ed2 100644 --- a/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.html @@ -1,4 +1,7 @@
+
+ +
@@ -87,14 +90,15 @@ + + + + @@ -115,7 +129,8 @@ @@ -123,7 +138,8 @@ + + + + + + +
+
+
Tag - +
Permissions - + Owner No Role Any Role @@ -103,11 +107,21 @@
+
+ +
+
Default Active - +
Stop Propagation - +
On errors - + {{item.label}} @@ -134,7 +150,8 @@ Timeout - +
Fallback step - +
Fallback tag - +
+ + expand_more + + Properties
+
@@ -171,7 +207,6 @@
- @@ -308,12 +344,12 @@ {{item.sourceTag}} - +
- {{getIcon(item)}} + {{getIcon(block)}}
- {{item.tag}} + {{block.tag}}
@@ -343,12 +379,12 @@ {{item.targetTag}} - +
- {{getIcon(item)}} + {{getIcon(block)}}
- {{item.tag}} + {{block.tag}}
@@ -379,8 +415,7 @@
diff --git a/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.ts b/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.ts index de36c0da38..b77a64bf9d 100644 --- a/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/common-properties/common-properties.component.ts @@ -1,7 +1,9 @@ import { Component, ComponentFactoryResolver, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; import { BlockErrorActions, GenerateUUIDv4, Schema, Token } from '@guardian/interfaces'; -import { IBlockAbout, RegisteredBlocks } from '../../registered-blocks'; -import { PolicyBlockModel, PolicyEventModel, PolicyModel } from '../../policy-model'; +import { ReplaySubject } from 'rxjs'; +import { RegisteredBlocks } from '../../registered-blocks'; +import { IBlockAbout } from "../../structures/interfaces/block-about.interface"; +import { PolicyBlockModel, PolicyEventModel, PolicyModel } from '../../structures/policy-model'; /** * Settings for all blocks. @@ -24,14 +26,16 @@ export class CommonPropertiesComponent implements OnInit { @Output() onInit = new EventEmitter(); + loading: boolean = true; propHidden: any = { about: true, metaData: false, + customProperties: false, eventsGroup: {} }; block!: PolicyBlockModel; - about!: IBlockAbout; + about!: IBlockAbout | undefined; errorActions = [ { label: 'No action', @@ -54,7 +58,8 @@ export class CommonPropertiesComponent implements OnInit { inputEvents: any[] = []; outputEvents: any[] = []; defaultEvent: boolean = false; - + customProperties!: any[] | undefined; + constructor( public registeredBlocks: RegisteredBlocks, private componentFactoryResolver: ComponentFactoryResolver @@ -174,6 +179,9 @@ export class CommonPropertiesComponent implements OnInit { if (!this.configContainer) { return; } + + this.about = undefined; + this.customProperties = undefined; setTimeout(() => { this.block = block; if (!this.block.properties.onErrorAction) { @@ -181,8 +189,19 @@ export class CommonPropertiesComponent implements OnInit { } this.configContainer.clear(); const factory: any = this.registeredBlocks.getProperties(block.blockType); + const customProperties = this.registeredBlocks.getCustomProperties(block.blockType); this.about = this.registeredBlocks.bindAbout(block.blockType, block); - if (factory) { + this.loadFactory(factory, customProperties); + }, 10); + } + + private loadFactory(factory: any, customProperties: any) { + if (factory) { + this.loading = true; + setTimeout(() => { + if (customProperties) { + this.customProperties = customProperties; + } let componentFactory = this.componentFactoryResolver.resolveComponentFactory(factory); let componentRef: any = this.configContainer.createComponent(componentFactory); componentRef.instance.policy = this.policy; @@ -190,13 +209,39 @@ export class CommonPropertiesComponent implements OnInit { componentRef.instance.schemas = this.schemas; componentRef.instance.tokens = this.tokens; componentRef.instance.readonly = this.readonly; - } - }) + setTimeout(() => { + this.loading = false; + }, 200); + }, 20); + } else if (customProperties) { + this.loading = true; + setTimeout(() => { + this.customProperties = customProperties; + setTimeout(() => { + this.loading = false; + }, 200); + }, 20); + } } onSave() { - if(this.block) { + if (this.block) { this.block.emitUpdate(); } } + + onChildrenApply(block: PolicyBlockModel, currentBlock: PolicyBlockModel) { + if (!block) { + return; + } + if (block.children) { + block.children.forEach(child => this.onChildrenApply(child, currentBlock)); + } + if (block !== currentBlock && currentBlock.permissions) { + block.silentlySetPermissions(currentBlock.permissions.slice()); + } + if (block === currentBlock) { + this.policy.emitUpdate(); + } + } } diff --git a/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.css b/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.html b/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.html new file mode 100644 index 0000000000..bfb069b329 --- /dev/null +++ b/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.html @@ -0,0 +1,103 @@ + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.ts b/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.ts new file mode 100644 index 0000000000..8b067144ff --- /dev/null +++ b/frontend/src/app/policy-engine/policy-configuration/common-property/common-property.component.ts @@ -0,0 +1,97 @@ +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; + +/** + * common property + */ +@Component({ + selector: '[common-property]', + templateUrl: './common-property.component.html', + styleUrls: ['./common-property.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class CommonPropertyComponent implements OnInit { + @Input('property') property!: any; + @Input('collapse') collapse!: any; + @Input('readonly') readonly!: boolean; + @Input('data') data!: any; + @Input('offset') offset!: number; + + @Output('update') update = new EventEmitter(); + + get value(): any { + return this.data[this.property.name]; + } + + set value(v: any) { + this.data[this.property.name] = v; + } + + get group(): any { + if (this.property.name) { + return this.data[this.property.name]; + } else { + return this.data; + } + } + + groupCollapse: boolean = false; + itemCollapse: any = {}; + + constructor() { + + } + + ngOnInit(): void { + + } + + ngOnChanges(changes: SimpleChanges) { + if (this.property) { + if (this.property.type === 'Group') { + if (typeof this.data[this.property.name] !== 'object') { + this.data[this.property.name] = {}; + } + } else if (this.property.type === 'Array') { + if (!Array.isArray(this.data[this.property.name])) { + this.data[this.property.name] = []; + } + } else { + if (this.property.default && !this.data.hasOwnProperty(this.property.name)) { + this.data[this.property.name] = this.property.default; + } + } + } + } + + customPropCollapse(property: any) { + return this.collapse; + } + + onSave() { + this.update.emit(); + } + + onHide() { + this.groupCollapse = !this.groupCollapse; + } + + onHideItem(i: any) { + this.itemCollapse[i] = !this.itemCollapse[i]; + } + + addItems() { + const item: any = {}; + for (const p of this.property.items.properties) { + if (p.default) { + item[p.name] = p.default; + } + } + this.value.push(item); + this.update.emit(); + } + + removeItems(i: number) { + this.value.splice(i, 1); + this.update.emit(); + } +} diff --git a/frontend/src/app/policy-engine/policy-configuration/json-properties/json-properties.component.ts b/frontend/src/app/policy-engine/policy-configuration/json-properties/json-properties.component.ts index a0ffa46aa7..214b40c74a 100644 --- a/frontend/src/app/policy-engine/policy-configuration/json-properties/json-properties.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/json-properties/json-properties.component.ts @@ -2,7 +2,7 @@ import { Component, ComponentFactoryResolver, EventEmitter, Input, OnInit, Outpu import { Schema, Token } from '@guardian/interfaces'; import { RegisteredBlocks } from '../../registered-blocks'; import { BlockNode } from '../../helpers/tree-data-source/tree-data-source'; -import { PolicyBlockModel } from '../../policy-model'; +import { PolicyBlockModel } from '../../structures/policy-model'; /** * Settings for all blocks. diff --git a/frontend/src/app/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts b/frontend/src/app/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts index 5d041c3308..f4e69baa6e 100644 --- a/frontend/src/app/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts @@ -7,15 +7,16 @@ import { forkJoin, Observable } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { TokenService } from 'src/app/services/token.service'; -import { BlockGroup, RegisteredBlocks } from '../../registered-blocks'; +import { RegisteredBlocks } from '../../registered-blocks'; +import { BlockGroup } from "../../structures/types/block-group.type"; import { PolicyAction, SavePolicyDialog } from '../../helpers/save-policy-dialog/save-policy-dialog.component'; import { SetVersionDialog } from 'src/app/schema-engine/set-version-dialog/set-version-dialog.component'; import * as yaml from 'js-yaml'; import { Clipboard } from '@angular/cdk/clipboard'; import { ConfirmationDialogComponent } from 'src/app/components/confirmation-dialog/confirmation-dialog.component'; import { EventsOverview } from '../../helpers/events-overview/events-overview'; -import { PolicyBlockModel, PolicyModel } from '../../policy-model'; -import { PolicyStorage } from '../../policy-storage'; +import { PolicyBlockModel, PolicyModel } from '../../structures/policy-model'; +import { PolicyStorage } from '../../structures/policy-storage'; import { TreeFlatOverview } from '../../helpers/tree-flat-overview/tree-flat-overview'; import { SaveBeforeDialogComponent } from '../../helpers/save-before-dialog/save-before-dialog.component'; import { TasksService } from 'src/app/services/tasks.service'; diff --git a/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.html b/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.html index 7858e3252e..084fc3b1d3 100644 --- a/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.html +++ b/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.html @@ -39,35 +39,35 @@ @@ -100,7 +100,7 @@ @@ -204,14 +204,14 @@ @@ -229,7 +229,7 @@ @@ -246,7 +246,7 @@ diff --git a/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.ts b/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.ts index 4420d71422..5234065a35 100644 --- a/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.ts +++ b/frontend/src/app/policy-engine/policy-configuration/policy-properties/policy-properties.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { PolicyModel, PolicyGroupModel, PolicyRoleModel, PolicyTopicModel } from '../../policy-model'; +import { PolicyModel, PolicyGroupModel, PolicyRoleModel, PolicyTopicModel } from '../../structures/policy-model'; /** * Settings for policy. diff --git a/frontend/src/app/policy-engine/policy-engine.module.ts b/frontend/src/app/policy-engine/policy-engine.module.ts index bde45a87d7..5842247cf9 100644 --- a/frontend/src/app/policy-engine/policy-engine.module.ts +++ b/frontend/src/app/policy-engine/policy-engine.module.ts @@ -68,6 +68,9 @@ import { GroupManagerConfigComponent } from './policy-configuration/blocks/main/ import { GroupManagerBlockComponent } from './policy-viewer/blocks/group-manager-block/group-manager-block.component'; import { InviteDialogComponent } from './helpers/invite-dialog/invite-dialog.component'; import { DocumentPath } from './helpers/document-path/document-path.component'; +import { CommonPropertyComponent } from './policy-configuration/common-property/common-property.component'; +import { MultiSignBlockComponent } from './policy-viewer/blocks/multi-sign-block/multi-sign-block.component'; +import { SelectBlock } from './helpers/select-block/select-block.component'; @NgModule({ declarations: [ @@ -130,7 +133,10 @@ import { DocumentPath } from './helpers/document-path/document-path.component'; GroupManagerConfigComponent, GroupManagerBlockComponent, InviteDialogComponent, - DocumentPath + DocumentPath, + CommonPropertyComponent, + MultiSignBlockComponent, + SelectBlock ], imports: [ CommonModule, diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.html b/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.html index eae6ecdbed..d5244c0858 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.html +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.html @@ -46,6 +46,9 @@ {{getText(element, option)}} + + {{getIssuer(element, option)}} +
{{getText(element, option)}}
diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts b/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts index 016032181e..2d6957798f 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/documents-source-block/documents-source-block.component.ts @@ -17,9 +17,9 @@ import { WebSocketService } from 'src/app/services/web-socket.service'; styleUrls: ['./documents-source-block.component.css'], animations: [ trigger('statusExpand', [ - state('collapsed', style({height: '0px', minHeight: '0'})), - state('expanded', style({height: '*'})), - transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + state('collapsed', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), ]), ] }) @@ -230,6 +230,32 @@ export class DocumentsSourceBlockComponent implements OnInit { } } + getIssuer(row: any, field: any) { + try { + if (field.content) { + return field.content; + } + if (field.names) { + let d = row[field.names[0]]; + for (let i = 1; i < field.names.length; i++) { + const name = field.names[i]; + d = d[name]; + } + if (typeof d === 'object') { + return d.id; + } + return d; + } else { + if (typeof row[field.name] === 'object') { + return row[field.name].id; + } + return row[field.name]; + } + } catch (error) { + return ""; + } + } + getGroup(row: any, field: any): any | null { const items = this.fieldMap[field.title]; if (items) { @@ -260,9 +286,15 @@ export class DocumentsSourceBlockComponent implements OnInit { } getConfig(row: any, field: any, block: any) { - const config = { ...block }; - config.data = row; - return config; + if (row.blocks && row.blocks[block.id]) { + const config = row.blocks[block.id]; + config.data = row; + return config; + } else { + const config = { ...block }; + config.data = row; + return config; + } } onButton(event: MouseEvent, row: any, field: any) { diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.css b/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.css new file mode 100644 index 0000000000..615d817c19 --- /dev/null +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.css @@ -0,0 +1,221 @@ +.loading { + background: #fff; + position: fixed; + z-index: 99; + top: var(--header-height-policy); + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; +} + +.btn-SIGN { + background: #4caf50; + border-radius: 6px; + color: #fff; + display: inline-block; + padding: 2px 8px; + margin: 0px 8px 0px 0px; + cursor: pointer; + font-weight: 500; + min-width: 105px; +} + +.btn-DECLINE { + background: #f44336; + border-radius: 6px; + color: #fff; + display: inline-block; + padding: 2px 8px; + margin: 0px 0px 0px 8px; + cursor: pointer; + font-weight: 500; + min-width: 105px; +} + +.status-NEW { + color: #0000ee; + font-weight: 500; +} + +.status-SIGNED { + color: #4caf50; + font-weight: 500; +} + +.status-DECLINED { + color: #f44336; + font-weight: 500; +} + +.status { + cursor: pointer; +} + +.btn-link { + cursor: pointer; + width: auto; + font-size: 20px; + color: #0B73F8; + text-decoration: none; + display: inline-block; + border-bottom: 3px solid #0C77FF; + padding-bottom: 3px; + font-weight: normal; + text-transform: capitalize; +} + +.content .btn-option { + height: 26px; + text-align: center; + border-radius: 60px !important; + position: relative; + height: 38px; + box-shadow: 0px 3px 6px #00000029; + border-radius: 24px !important; + padding: 8px 20px !important; + box-sizing: border-box; + font-size: 20px; +} + +.content { + display: flex; + justify-content: center; +} + +.progress { + height: 16px; + background: #ebebeb; + border: 1px solid #a1a1a1; + border-radius: 2px; + position: relative; + cursor: pointer; + width: 190px; +} + +.signed { + background: #4caf50; + position: absolute; + height: 100%; + left: 0; + pointer-events: none; +} + +.declined { + background: #f44336; + position: absolute; + height: 100%; + right: 0; + pointer-events: none; +} + +.threshold { + position: absolute; + width: 3px; + background: #777; + top: -6px; + bottom: -6px; + pointer-events: none; + transform: translate(-1px, 0px); +} + +.documents { + padding: 5px 16px 5px 40px; + display: grid; + grid-template-columns: auto 100px; + max-width: 600px; +} + +.doc-did { + overflow: hidden; + margin-right: 10px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.doc-status { + text-align: end; +} + +.p-value { + position: absolute; + color: #fff; + font-size: 12px; + left: 0; + right: 0; + text-align: center; + height: 16px; + line-height: 16px; +} + +.result { + display: flex; + padding: 5px 14px 5px 40px; +} + +.result-status { + font-size: 16px; + width: 100px; +} + +.result-progress { + display: grid; + grid-template-columns: auto 220px auto; +} + +.result-progress-bar { + height: 16px; + background: #ebebeb; + border: 1px solid #a1a1a1; + border-radius: 2px; + position: relative; + cursor: pointer; + width: 198px; + margin: 0px 10px; +} + +.result-progress-signed, +.result-progress-declined { + position: absolute; + height: 100%; + left: 0; + pointer-events: none; + text-align: center; + color: #fff; + line-height: 17px; +} + +.result-progress-signed { + background: #4caf50; +} + +.result-progress-declined { + background: #f44336; +} + +.details { + color: #4e4e4e; + font-weight: 500; + font-size: 16px; + width: 370px; + padding: 5px 14px; +} + +.icon-details { + color: #0B73F8; + position: absolute; + top: 2px; + right: 0; + pointer-events: none; +} + +.progress-container { + position: relative; + width: 200px; + padding: 5px 22px 5px 0px; + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.html b/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.html new file mode 100644 index 0000000000..1a5bc84d1b --- /dev/null +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.html @@ -0,0 +1,87 @@ +
+ +
+ +
+
+
+ {{confirmationStatus}} +
+ +
+
+
{{doc.username}}
+
{{doc.status}}
+
+
+
+
+
+
+ Sign +
+
+ Decline +
+
+
+
+
+
+ {{signedCount}} +
+
+ {{declinedCount}} +
+
+
+
+ info_outline +
+
+ +
+
+
Required number of signatures
+
+
Signed:
+
+
0
+
+
+ {{signedCount}} +
+
+
{{signedMax}}
+
+
+
+
Declined:
+
+
0
+
+
+ {{declinedCount}} +
+
+
{{declinedMax}}
+
+
+
+
Details
+
+
+
{{doc.username}}
+
{{doc.status}}
+
+
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.ts b/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.ts new file mode 100644 index 0000000000..32365cfcf2 --- /dev/null +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/multi-sign-block/multi-sign-block.component.ts @@ -0,0 +1,141 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { PolicyHelper } from 'src/app/services/policy-helper.service'; +import { WebSocketService } from 'src/app/services/web-socket.service'; + +/** + * Component for display block of 'multiSignBlock' types. + */ +@Component({ + selector: 'app-multi-sign-block', + templateUrl: './multi-sign-block.component.html', + styleUrls: ['./multi-sign-block.component.css'] +}) +export class MultiSignBlockComponent implements OnInit { + @Input('id') id!: string; + @Input('policyId') policyId!: string; + @Input('static') static!: any; + + isActive = false; + loading: boolean = true; + socket: any; + + data: any; + status: any; + confirmationStatus: any; + documentStatus: any; + total: any; + documents: any; + declined: any; + signed: any; + threshold: any; + declinedCount: any; + signedCount: any; + signedMax: any; + declinedMax: any; + + constructor( + private policyEngineService: PolicyEngineService, + private wsService: WebSocketService, + private policyHelper: PolicyHelper, + ) { + } + + ngOnInit(): void { + if (!this.static) { + this.socket = this.wsService.blockSubscribe(this.onUpdate.bind(this)); + } + this.loadData(); + } + + ngOnDestroy(): void { + if (this.socket) { + this.socket.unsubscribe(); + } + } + + onUpdate(id: string): void { + if (this.id == id) { + this.loadData(); + } + } + + loadData() { + this.loading = true; + if (this.static) { + this.setData(this.static); + setTimeout(() => { + this.loading = false; + }, 500); + } else { + this.loading = true; + this.policyEngineService.getBlockData(this.id, this.policyId).subscribe((data: any) => { + this.setData(data); + this.loading = false; + }, (e) => { + console.error(e.error); + this.loading = false; + }); + } + } + + setData(data: any) { + if (data) { + this.data = data.data; + this.status = data.status || {}; + this.confirmationStatus = this.status.confirmationStatus; + this.documentStatus = this.status.documentStatus; + this.total = this.status.total; + this.documents = this.status.data || []; + if (this.total) { + this.declined = this.status.declinedPercent; + this.signed = this.status.signedPercent; + this.declinedCount = this.status.declinedCount; + this.signedCount = this.status.signedCount; + this.threshold = this.status.threshold; + this.signedMax = this.status.signedThreshold; + this.declinedMax = this.status.declinedThreshold; + } else { + this.declined = 0; + this.signed = 0; + this.declinedCount = 0; + this.signedCount = 0; + this.threshold = 50; + this.signedMax = 0; + this.declinedMax = 0; + } + this.isActive = true; + } else { + this.isActive = false; + } + } + + onSelect(status: any) { + this.loading = true; + const data = { + document: { + id: this.data.id + }, + status + } + this.policyEngineService.setBlockData(this.id, this.policyId, data).subscribe(() => { + setTimeout(() => { + this.loading = false; + }, 1000); + }, (e) => { + console.error(e.error); + this.loading = false; + }); + } + + onDetails() { + + } + + onClickMenu(event:any) { + if(event.stopPropagation) { + event.stopPropagation(); + } + return false; + } +} diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.css b/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.css index 2df87c58bc..41841d20f9 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.css +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.css @@ -134,7 +134,7 @@ a[disabled="true"]{ user-select: none; cursor: pointer; height: 220px; - margin-top: 80px; + margin-top: 50px; } .scroll-right:hover { @@ -151,12 +151,13 @@ a[disabled="true"]{ flex-direction: column; align-content: center; align-items: center; */ - margin-top: 60px; + margin-top: 30px; margin-left: 00px; margin-right: 20px; - padding-top: 20px; + padding-top: 35px; white-space: nowrap; overflow-x: auto; + padding-bottom: 5px; } .chain-item { @@ -420,4 +421,87 @@ a.open-vp { position: absolute; left: 16px; top: 37px; -} \ No newline at end of file +} + +.multiple-documents-container { + position: relative; + display: inline-block; +} + +.multiple-documents-container .chain-item +{ + transition: transform 1s ease; +} + +.active-multiple-document { + transform: translate(0,0); + z-index: 3; + border: 2px solid gray; +} + +.second-multiple-document { + position: absolute; + left: 0; + bottom: 0; + z-index: 2; + transform: translate(-7px, -7px); + border: 2px solid #9d9d9d; +} + +.third-multiple-document { + position: absolute; + left: 0; + bottom: 0; + z-index: 1; + transform: translate(-14px, -14px); + border: 2px solid #bfbfbf; +} + +.single-multiple-document { + z-index: 1; +} + +.hide-multiple-document { + display: none; +} + +.multiple-documents-count { + position: absolute; + right: 42px; + bottom: 12px; + font-size: 18px; + color: #707070; + font-weight: bold; + display: flex; + align-items: center; +} + +.multiple-documents-count mat-icon { + cursor: pointer; +} + +.item-relationships-icon { + position: absolute; + z-index: 3; + transform: rotate(180deg); + width: 32px; + height: 34px; + background: white; + font-size: 35px; + left: -11px; + bottom: 45%; + border-radius: 10px 5px 5px 27px; + color: gray; +} + +.item-relationships-delimiter { + height: 100%; + width: 20px; + position: absolute; + left: -36px; + top: -5px; + border-right: 2px solid gray; + border-top: 2px solid gray; + border-bottom: 2px solid gray; + transition: transform 1s linear; +} diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.html b/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.html index 40d429f206..68d80e6b73 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.html +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.html @@ -43,7 +43,7 @@
Token & Issuer - VC Files + VC File
@@ -64,7 +64,7 @@
Policy Overview - VC Files + VC File
@@ -107,40 +107,62 @@
-
-
- - {{item.icon}} - - - - {{item.title}} -
-
- warning - Revoked with reason: "{{item.document.comment}}" -
-
- {{item.description}} -
-
-
Parties:
-
-
- {{item.username}} +
+
+
+ account_tree +
+
+ + {{item.icon}} + + + {{item.title}} +
+
+ warning + Revoked with reason: "{{document.document.comment}}" +
+
+ {{item.description}} +
+
+
Parties:
+
+
+ {{document.username}} +
+
+
+ +
+ VC File +
+
+ chevron_left + {{item.activeDocumentIndex}} in {{item.document.length}} + chevron_right
- -
- VC Files -
diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts b/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts index 6ca42aa98e..1f7d88cb60 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts @@ -31,7 +31,7 @@ export class ReportBlockComponent implements OnInit { vcDocument: IVCReport | undefined; mintDocument: ITokenReport | undefined; policyDocument: IPolicyReport | undefined; - documents: IReportItem[] | undefined; + documents: any; policyCreatorDocument: IReportItem | undefined; searchForm = this.fb.group({ value: ['', Validators.required], @@ -135,18 +135,31 @@ export class ReportBlockComponent implements OnInit { if (this.policyCreatorDocument) { this.documents.push(this.policyCreatorDocument); } - this.documents = this.documents.filter(e=>e.visible); + this.documents = this.documents.filter((e: any)=>e.visible); this.documents = this.documents.reverse(); + this.documents.forEach((reportItem: any) => { + if (!Array.isArray(reportItem.document) && reportItem.document) { + reportItem.document = [{ + document: reportItem.document, + tag: reportItem.tag, + issuer: reportItem.issuer, + username: reportItem.username, + }]; + } + if (reportItem.multiple && reportItem.document[0]) { + this.onMultipleDocumentClick(reportItem.document[0], reportItem); + } + }); this.loading = false; } - openVCDocument(item: IVCReport | ITokenReport | IPolicyReport | IReportItem) { + openVCDocument(item: IVCReport | ITokenReport | IPolicyReport | IReportItem, document?: any) { const dialogRef = this.dialog.open(VCViewerDialog, { width: '850px', data: { viewDocument: true, - document: item.document.document, + document: document || item.document.document, title: item.type, type: 'VC' } @@ -215,4 +228,86 @@ export class ReportBlockComponent implements OnInit { this.loading = false; }); } + + dynamicSortItems(item?: any, activeDocument?: any) { + if (!item + || !this.documents + || !activeDocument + || !item.dynamicFilters + || !item.dynamicFilters.length) { + return; + } + const itemIndex = this.documents.indexOf(item); + const prevReportItem = this.documents[itemIndex - 1]; + if (!prevReportItem) { + return; + } + prevReportItem.allDocuments = prevReportItem.allDocuments || JSON.parse(JSON.stringify(prevReportItem.document)); + for (const dynamicFilter of item.dynamicFilters) { + this.applyFilters(prevReportItem, activeDocument, dynamicFilter.field, dynamicFilter.nextItemField, dynamicFilter.type); + } + this.onMultipleDocumentClick(prevReportItem.document[0], prevReportItem); + } + + applyFilters(reportItemToSort: any, activeDocument: any, field: string, nextItemField: string, type: string) { + switch (type) { + case 'equal': + reportItemToSort.document = reportItemToSort.allDocuments.filter((item: any) => item.document[nextItemField] === activeDocument.document[field]); + break; + case 'not_equal': + reportItemToSort.document = reportItemToSort.allDocuments.filter((item: any) => item.document[nextItemField] !== activeDocument.document[field]); + break; + case 'in': + reportItemToSort.document = reportItemToSort.allDocuments.filter((item: any) => activeDocument.document[field].includes(item.document[nextItemField])); + break; + case 'not_in': + reportItemToSort.document = reportItemToSort.allDocuments.filter((item: any) => !activeDocument.document[field].includes(item.document[nextItemField])); + break; + } + } + + onMultipleDocumentClick(activeDocument?: any, item?: any) { + if (!item || !item.multiple || !activeDocument) { + return; + } + + const itemDocuments = item.document; + const oldActiveDocument = itemDocuments.find((item: any) => item.index === 0); + if (oldActiveDocument == activeDocument) { + return; + } + + itemDocuments.forEach((element: any) => { + element.index = undefined; + element.color = undefined; + }); + activeDocument.index = 0; + const indexDocument = itemDocuments.indexOf(activeDocument); + if (itemDocuments.length > 1) { + const secondDocumentIndex = (indexDocument + 1) % itemDocuments.length; + itemDocuments[secondDocumentIndex].index = 1; + } + if (itemDocuments.length > 2) { + const thirdDocumentIndex = (indexDocument + 2) % itemDocuments.length; + itemDocuments[thirdDocumentIndex].index = 2; + } + item.activeDocumentIndex = item.document.indexOf(activeDocument) + 1; + this.dynamicSortItems(item, activeDocument); + } + + onNextDocumentClick(event: any, item: any, document: any) { + event.stopPropagation(); + const itemDocuments = item.document; + const indexDocument = itemDocuments.indexOf(document); + const secondDocumentIndex = (indexDocument + 1) % itemDocuments.length; + this.onMultipleDocumentClick(itemDocuments[secondDocumentIndex], item); + } + + onPrevDocumentClick(event: any, item: any, document: any) { + event.stopPropagation(); + const itemDocuments = item.document; + const indexDocument = itemDocuments.indexOf(document); + const secondDocumentIndex = (indexDocument - 1) < 0 ? itemDocuments.length + (indexDocument - 1) : (indexDocument - 1); + this.onMultipleDocumentClick(itemDocuments[secondDocumentIndex], item); + } } diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.html b/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.html index 2105d324bf..4c16588bd1 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.html +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.html @@ -32,7 +32,7 @@ {{group}} - + Group Label diff --git a/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.ts b/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.ts index 617f806f6b..603c8fab06 100644 --- a/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.ts +++ b/frontend/src/app/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.ts @@ -28,6 +28,7 @@ export class RolesBlockComponent implements OnInit { description?: any; roleForm: FormGroup; type: any = 'new'; + groupMap: any; params: any; inviteRole: string = ''; @@ -98,6 +99,8 @@ export class RolesBlockComponent implements OnInit { this.groups = data.groups; this.roles = data.roles; + this.groupMap = data.groupMap || {}; + this.title = uiMetaData.title; this.description = uiMetaData.description; this.isGroup = !!(this.groups && this.groups.length); @@ -113,7 +116,7 @@ export class RolesBlockComponent implements OnInit { } else { this.roleForm = this.fb.group({ roleOrGroup: ['', Validators.required], - groupLabel: this.isGroup ? ['', Validators.required] : [''], + groupLabel: [''], invitation: [''], }); } @@ -157,7 +160,7 @@ export class RolesBlockComponent implements OnInit { if (this.type === 'new') { this.roleForm = this.fb.group({ roleOrGroup: ['', Validators.required], - groupLabel: this.isGroup ? ['', Validators.required] : [''], + groupLabel: [''], invitation: [''], }); } else { @@ -190,4 +193,11 @@ export class RolesBlockComponent implements OnInit { } } } + + ifPrivateGroup() { + if (this.groupMap && this.groupMap[this.roleForm.value.roleOrGroup]) { + return this.groupMap[this.roleForm.value.roleOrGroup].groupAccessType === 'Private'; + } + return false; + } } \ No newline at end of file diff --git a/frontend/src/app/policy-engine/registered-blocks.ts b/frontend/src/app/policy-engine/registered-blocks.ts index 281ba01e25..535b96e539 100644 --- a/frontend/src/app/policy-engine/registered-blocks.ts +++ b/frontend/src/app/policy-engine/registered-blocks.ts @@ -29,7 +29,7 @@ import { ReassigningConfigComponent } from "./policy-configuration/blocks/docume import { TimerConfigComponent } from "./policy-configuration/blocks/documents/timer-config/timer-config.component"; import { CustomLogicConfigComponent } from './policy-configuration/blocks/calculate/custom-logic-config/custom-logic-config.component'; import { SwitchConfigComponent } from "./policy-configuration/blocks/main/switch-config/switch-config.component"; -import { PolicyBlockModel } from "./policy-model"; +import { PolicyBlockModel } from "./structures/policy-model"; import { RevokeConfigComponent } from "./policy-configuration/blocks/documents/revoke-config/revoke-config.component"; import { ButtonConfigComponent } from "./policy-configuration/blocks/main/button-config/button-config.component"; import { ButtonBlockComponent } from "./policy-viewer/blocks/button-block/button-block.component"; @@ -40,193 +40,15 @@ import { TokenConfirmationConfigComponent } from "./policy-configuration/blocks/ import { TokenConfirmationBlockComponent } from "./policy-viewer/blocks/token-confirmation-block/token-confirmation-block.component"; import { GroupManagerConfigComponent } from './policy-configuration/blocks/main/group-manager-config/group-manager-config.component'; import { GroupManagerBlockComponent } from './policy-viewer/blocks/group-manager-block/group-manager-block.component'; - -export enum BlockType { - Container = 'interfaceContainerBlock', - DocumentsViewer = 'interfaceDocumentsSourceBlock', - Information = 'informationBlock', - PolicyRoles = 'policyRolesBlock', - Request = 'requestVcDocumentBlock', - SendToGuardian = 'sendToGuardianBlock', - Action = 'interfaceActionBlock', - Step = 'interfaceStepBlock', - Mint = 'mintDocumentBlock', - ExternalData = 'externalDataBlock', - AggregateDocument = 'aggregateDocumentBlock', - Wipe = 'retirementDocumentBlock', - FiltersAddon = 'filtersAddon', - DocumentsSourceAddon = 'documentsSourceAddon', - Calculate = 'calculateContainerBlock', - CalculateMathAddon = 'calculateMathAddon', - Report = 'reportBlock', - ReportItem = 'reportItemBlock', - ReassigningBlock = 'reassigningBlock', - PaginationAddon = 'paginationAddon', - TimerBlock = 'timerBlock', - CustomLogicBlock = 'customLogicBlock', - Switch = 'switchBlock', - RevokeBlock = 'revokeBlock', - SetRelationshipsBlock = 'setRelationshipsBlock', - ButtonBlock = 'buttonBlock', - TokenActionBlock = 'tokenActionBlock', - DocumentValidatorBlock = 'documentValidatorBlock', - TokenConfirmationBlock = 'tokenConfirmationBlock', - GroupManagerBlock = 'groupManagerBlock' -} - -export enum BlockGroup { - Main = 'Main', - Documents = 'Documents', - Tokens = 'Tokens', - Calculate = 'Calculate', - Report = 'Report', - UnGrouped = 'UnGrouped' -} - -export enum BlockHeaders { - UIComponents = 'UI Components', - ServerBlocks = 'Server Blocks', - Addons = 'Addons' -} - -export interface IBlockAbout { - post: boolean; - get: boolean; - input: any; - output: any; - children: ChildrenType; - control: ControlType; - defaultEvent: boolean; - prev?: IBlockAbout; - next?: boolean; -} - -type ConfigFunction = ((value: any, block: PolicyBlockModel, prev?: IBlockAbout, next?: boolean) => T) | T; - -export interface IBlockAboutConfig { - post: boolean; - get: boolean; - input: any; - output: any; - children: ChildrenType; - control: ControlType; - defaultEvent: boolean; -} - -export interface IBlockDynamicAboutConfig { - post?: ConfigFunction; - get?: ConfigFunction; - input?: ConfigFunction; - output?: ConfigFunction; - children?: ConfigFunction; - control?: ConfigFunction; - defaultEvent?: ConfigFunction; -} - -export enum ChildrenType { - None = 'None', - Special = 'Special', - Any = 'Any', -} - -export enum ControlType { - UI = 'UI', - Special = 'Special', - Server = 'Server', - None = 'None', -} - -export class BlockAbout { - private _propFunc: { [x: string]: Function } = {}; - private _propVal: { [x: string]: any } = {}; - private _setProp(about: any, dynamic: any, name: string) { - this._propVal[name] = about[name]; - if (dynamic && dynamic[name]) { - this._propFunc[name] = dynamic[name]; - } else { - this._propFunc[name] = (value: any, block: any, prev?: IBlockAbout, next?: boolean) => { - return this._propVal[name]; - }; - } - } - - constructor(about: IBlockAboutConfig, dynamic?: IBlockDynamicAboutConfig) { - this._setProp(about, dynamic, 'post'); - this._setProp(about, dynamic, 'get'); - this._setProp(about, dynamic, 'input'); - this._setProp(about, dynamic, 'output'); - this._setProp(about, dynamic, 'children'); - this._setProp(about, dynamic, 'control'); - this._setProp(about, dynamic, 'defaultEvent'); - } - - public get(block: PolicyBlockModel): IBlockAbout { - return { - post: this._propFunc.post(this._propVal.post, block), - get: this._propFunc.get(this._propVal.get, block), - input: this._propFunc.input(this._propVal.input, block), - output: this._propFunc.output(this._propVal.output, block), - children: this._propFunc.children(this._propVal.children, block), - control: this._propFunc.control(this._propVal.control, block), - defaultEvent: this._propFunc.defaultEvent(this._propVal.defaultEvent, block), - } - } - - public bind(block: PolicyBlockModel, prev?: IBlockAbout, next?: boolean): IBlockAbout { - const bind = { - _block: block, - _prev: prev, - _next: next, - _func: this._propFunc, - _val: this._propVal, - get post() { - return this._func.post(this._val.post, this._block, this._prev, this._next); - }, - get get() { - return this._func.get(this._val.get, this._block, this._prev, this._next); - }, - get input() { - return this._func.input(this._val.input, this._block, this._prev, this._next); - }, - get output() { - return this._func.output(this._val.output, this._block, this._prev, this._next); - }, - get children() { - return this._func.children(this._val.children, this._block, this._prev, this._next); - }, - get control() { - return this._func.control(this._val.control, this._block, this._prev, this._next); - }, - get defaultEvent() { - return this._func.defaultEvent(this._val.defaultEvent, this._block, this._prev, this._next); - }, - set prev(value: IBlockAbout) { - this._prev = value; - }, - set next(value: boolean) { - this._next = value; - } - } - return bind - } -} - -export interface IBlockSetting { - type: BlockType; - icon: string; - group: BlockGroup; - header: BlockHeaders; - factory: any; - property: any; - allowedChildren?: ChildrenDisplaySettings[]; - about?: IBlockDynamicAboutConfig -} - -export interface ChildrenDisplaySettings { - type: BlockType, - group?: BlockGroup, - header?: BlockHeaders -} +import { BlockType } from "./structures/types/block-type.type"; +import { BlockGroup } from "./structures/types/block-group.type"; +import { BlockHeaders } from "./structures/types/block-headers.type"; +import { IBlockAbout } from "./structures/interfaces/block-about.interface"; +import { ChildrenType } from "./structures/types/children-type.type"; +import { ControlType } from "./structures/types/control-type.type"; +import { BlockAbout } from "./structures/block-about"; +import { IBlockSetting } from "./structures/interfaces/block-setting.interface"; +import { MultiSignBlockComponent } from "./policy-viewer/blocks/multi-sign-block/multi-sign-block.component"; @Injectable() export class RegisteredBlocks { @@ -240,6 +62,7 @@ export class RegisteredBlocks { private about: any; private allowedChildren: any; private dynamicAbout: any; + private customProperties: any; private readonly defaultA = new BlockAbout({ post: false, @@ -262,6 +85,7 @@ export class RegisteredBlocks { this.about = {}; this.allowedChildren = {}; this.dynamicAbout = {}; + this.customProperties = {}; const allowedChildrenStepContainerBlocks = [ { type: BlockType.Information }, @@ -288,7 +112,8 @@ export class RegisteredBlocks { { type: BlockType.ButtonBlock }, { type: BlockType.TokenActionBlock }, { type: BlockType.TokenConfirmationBlock }, - { type: BlockType.DocumentValidatorBlock } + { type: BlockType.DocumentValidatorBlock }, + { type: BlockType.MultiSignBlock } ]; // Main, UI Components @@ -420,15 +245,8 @@ export class RegisteredBlocks { header: BlockHeaders.UIComponents, factory: DocumentsSourceBlockComponent, property: DocumentSourceComponent, - allowedChildren: [{ - type: BlockType.FiltersAddon, - group: BlockGroup.UnGrouped - }, { - type: BlockType.DocumentsSourceAddon, - group: BlockGroup.UnGrouped - }, { - type: BlockType.PaginationAddon, - group: BlockGroup.UnGrouped + allowedChildren: [...allowedChildrenStepContainerBlocks, { + type: BlockType.PaginationAddon }] }); this.registerBlock({ @@ -446,6 +264,14 @@ export class RegisteredBlocks { group: BlockGroup.UnGrouped }] }); + this.registerBlock({ + type: BlockType.MultiSignBlock, + icon: 'done_all', + group: BlockGroup.Documents, + header: BlockHeaders.UIComponents, + factory: MultiSignBlockComponent, + property: null, + }); // Documents, Server Blocks this.registerBlock({ @@ -588,7 +414,7 @@ export class RegisteredBlocks { factory: TokenConfirmationBlockComponent, property: TokenConfirmationConfigComponent, }); - + // Calculate, Server Blocks this.registerBlock({ type: BlockType.Calculate, @@ -673,6 +499,7 @@ export class RegisteredBlocks { control: setting.control, defaultEvent: setting.defaultEvent }, this.dynamicAbout[type]); + this.customProperties[type] = setting?.properties; } } @@ -680,6 +507,7 @@ export class RegisteredBlocks { this.names = {}; this.titles = {}; this.about = {}; + this.customProperties = {}; } public getHeader(blockType: string): string { @@ -728,6 +556,10 @@ export class RegisteredBlocks { return f.bind(block, prev, next); } + public getCustomProperties(blockType: string): any[] { + return this.customProperties[blockType]; + } + public newBlock(type: BlockType): BlockNode { return { id: GenerateUUIDv4(), diff --git a/frontend/src/app/policy-engine/structures/block-about.ts b/frontend/src/app/policy-engine/structures/block-about.ts new file mode 100644 index 0000000000..4ca6a7d9ca --- /dev/null +++ b/frontend/src/app/policy-engine/structures/block-about.ts @@ -0,0 +1,79 @@ +import { PolicyBlockModel } from "./policy-model"; +import { IBlockAbout } from "./interfaces/block-about.interface"; +import { IBlockAboutConfig } from "./interfaces/block-about-config.interface"; +import { IBlockDynamicAboutConfig } from "./interfaces/block-dynamic-about-config.interface"; + +export class BlockAbout { + private _propFunc: { [x: string]: Function; } = {}; + private _propVal: { [x: string]: any; } = {}; + private _setProp(about: any, dynamic: any, name: string) { + this._propVal[name] = about[name]; + if (dynamic && dynamic[name]) { + this._propFunc[name] = dynamic[name]; + } else { + this._propFunc[name] = (value: any, block: any, prev?: IBlockAbout, next?: boolean) => { + return this._propVal[name]; + }; + } + } + + constructor(about: IBlockAboutConfig, dynamic?: IBlockDynamicAboutConfig) { + this._setProp(about, dynamic, 'post'); + this._setProp(about, dynamic, 'get'); + this._setProp(about, dynamic, 'input'); + this._setProp(about, dynamic, 'output'); + this._setProp(about, dynamic, 'children'); + this._setProp(about, dynamic, 'control'); + this._setProp(about, dynamic, 'defaultEvent'); + } + + public get(block: PolicyBlockModel): IBlockAbout { + return { + post: this._propFunc.post(this._propVal.post, block), + get: this._propFunc.get(this._propVal.get, block), + input: this._propFunc.input(this._propVal.input, block), + output: this._propFunc.output(this._propVal.output, block), + children: this._propFunc.children(this._propVal.children, block), + control: this._propFunc.control(this._propVal.control, block), + defaultEvent: this._propFunc.defaultEvent(this._propVal.defaultEvent, block), + }; + } + + public bind(block: PolicyBlockModel, prev?: IBlockAbout, next?: boolean): IBlockAbout { + const bind = { + _block: block, + _prev: prev, + _next: next, + _func: this._propFunc, + _val: this._propVal, + get post() { + return this._func.post(this._val.post, this._block, this._prev, this._next); + }, + get get() { + return this._func.get(this._val.get, this._block, this._prev, this._next); + }, + get input() { + return this._func.input(this._val.input, this._block, this._prev, this._next); + }, + get output() { + return this._func.output(this._val.output, this._block, this._prev, this._next); + }, + get children() { + return this._func.children(this._val.children, this._block, this._prev, this._next); + }, + get control() { + return this._func.control(this._val.control, this._block, this._prev, this._next); + }, + get defaultEvent() { + return this._func.defaultEvent(this._val.defaultEvent, this._block, this._prev, this._next); + }, + set prev(value: IBlockAbout) { + this._prev = value; + }, + set next(value: boolean) { + this._next = value; + } + }; + return bind; + } +} diff --git a/frontend/src/app/policy-engine/structures/interfaces/block-about-config.interface.ts b/frontend/src/app/policy-engine/structures/interfaces/block-about-config.interface.ts new file mode 100644 index 0000000000..e317ad2563 --- /dev/null +++ b/frontend/src/app/policy-engine/structures/interfaces/block-about-config.interface.ts @@ -0,0 +1,14 @@ +import { ControlType } from "../types/control-type.type"; +import { ChildrenType } from "../types/children-type.type"; + + + +export interface IBlockAboutConfig { + post: boolean; + get: boolean; + input: any; + output: any; + children: ChildrenType; + control: ControlType; + defaultEvent: boolean; +} diff --git a/frontend/src/app/policy-engine/structures/interfaces/block-about.interface.ts b/frontend/src/app/policy-engine/structures/interfaces/block-about.interface.ts new file mode 100644 index 0000000000..603bd52ef8 --- /dev/null +++ b/frontend/src/app/policy-engine/structures/interfaces/block-about.interface.ts @@ -0,0 +1,14 @@ +import { ControlType } from "../types/control-type.type"; +import { ChildrenType } from "../types/children-type.type"; + +export interface IBlockAbout { + post: boolean; + get: boolean; + input: any; + output: any; + children: ChildrenType; + control: ControlType; + defaultEvent: boolean; + prev?: IBlockAbout; + next?: boolean; +} diff --git a/frontend/src/app/policy-engine/structures/interfaces/block-dynamic-about-config.interface.ts b/frontend/src/app/policy-engine/structures/interfaces/block-dynamic-about-config.interface.ts new file mode 100644 index 0000000000..9d8de0962b --- /dev/null +++ b/frontend/src/app/policy-engine/structures/interfaces/block-dynamic-about-config.interface.ts @@ -0,0 +1,16 @@ +import { ChildrenType } from "../types/children-type.type"; +import { ControlType } from "../types/control-type.type"; +import { IBlockAbout } from "./block-about.interface"; +import { PolicyBlockModel } from "../policy-model"; + +type ConfigFunction = ((value: any, block: PolicyBlockModel, prev?: IBlockAbout, next?: boolean) => T) | T; + +export interface IBlockDynamicAboutConfig { + post?: ConfigFunction; + get?: ConfigFunction; + input?: ConfigFunction; + output?: ConfigFunction; + children?: ConfigFunction; + control?: ConfigFunction; + defaultEvent?: ConfigFunction; +} diff --git a/frontend/src/app/policy-engine/structures/interfaces/block-setting.interface.ts b/frontend/src/app/policy-engine/structures/interfaces/block-setting.interface.ts new file mode 100644 index 0000000000..0acb72305a --- /dev/null +++ b/frontend/src/app/policy-engine/structures/interfaces/block-setting.interface.ts @@ -0,0 +1,16 @@ +import { BlockType } from "../types/block-type.type"; +import { BlockGroup } from "../types/block-group.type"; +import { BlockHeaders } from "../types/block-headers.type"; +import { ChildrenDisplaySettings } from "./children-display-settings.interface"; +import { IBlockDynamicAboutConfig } from "./block-dynamic-about-config.interface"; + +export interface IBlockSetting { + type: BlockType; + icon: string; + group: BlockGroup; + header: BlockHeaders; + factory: any; + property: any; + allowedChildren?: ChildrenDisplaySettings[]; + about?: IBlockDynamicAboutConfig; +} diff --git a/frontend/src/app/policy-engine/structures/interfaces/children-display-settings.interface.ts b/frontend/src/app/policy-engine/structures/interfaces/children-display-settings.interface.ts new file mode 100644 index 0000000000..160fe745cb --- /dev/null +++ b/frontend/src/app/policy-engine/structures/interfaces/children-display-settings.interface.ts @@ -0,0 +1,9 @@ +import { BlockGroup } from "../types/block-group.type"; +import { BlockHeaders } from "../types/block-headers.type"; +import { BlockType } from "../types/block-type.type"; + +export interface ChildrenDisplaySettings { + type: BlockType; + group?: BlockGroup; + header?: BlockHeaders; +} diff --git a/frontend/src/app/policy-engine/policy-model.ts b/frontend/src/app/policy-engine/structures/policy-model.ts similarity index 99% rename from frontend/src/app/policy-engine/policy-model.ts rename to frontend/src/app/policy-engine/structures/policy-model.ts index 3b65dc8219..3869a24195 100644 --- a/frontend/src/app/policy-engine/policy-model.ts +++ b/frontend/src/app/policy-engine/structures/policy-model.ts @@ -515,12 +515,16 @@ export class PolicyBlockModel { } public set permissions(value: string[]) { + this.silentlySetPermissions(value); + this.changed = true; + } + + public silentlySetPermissions(value: string[]) { if (Array.isArray(value)) { this.properties.permissions = value; } else { this.properties.permissions = []; } - this.changed = true; } public get children(): PolicyBlockModel[] { @@ -865,7 +869,8 @@ export class PolicyModel { const e = new PolicyGroupModel({ name: '', creator: '', - members: [] + members: [], + groupRelationshipType: GroupRelationshipType.Multiple }, this); this.addGroup(e); } diff --git a/frontend/src/app/policy-engine/policy-storage.ts b/frontend/src/app/policy-engine/structures/policy-storage.ts similarity index 100% rename from frontend/src/app/policy-engine/policy-storage.ts rename to frontend/src/app/policy-engine/structures/policy-storage.ts diff --git a/frontend/src/app/policy-engine/structures/types/block-group.type.ts b/frontend/src/app/policy-engine/structures/types/block-group.type.ts new file mode 100644 index 0000000000..abc82de978 --- /dev/null +++ b/frontend/src/app/policy-engine/structures/types/block-group.type.ts @@ -0,0 +1,9 @@ + +export enum BlockGroup { + Main = 'Main', + Documents = 'Documents', + Tokens = 'Tokens', + Calculate = 'Calculate', + Report = 'Report', + UnGrouped = 'UnGrouped' +} diff --git a/frontend/src/app/policy-engine/structures/types/block-headers.type.ts b/frontend/src/app/policy-engine/structures/types/block-headers.type.ts new file mode 100644 index 0000000000..ca814eede5 --- /dev/null +++ b/frontend/src/app/policy-engine/structures/types/block-headers.type.ts @@ -0,0 +1,6 @@ + +export enum BlockHeaders { + UIComponents = 'UI Components', + ServerBlocks = 'Server Blocks', + Addons = 'Addons' +} diff --git a/frontend/src/app/policy-engine/structures/types/block-type.type.ts b/frontend/src/app/policy-engine/structures/types/block-type.type.ts new file mode 100644 index 0000000000..f62775932a --- /dev/null +++ b/frontend/src/app/policy-engine/structures/types/block-type.type.ts @@ -0,0 +1,34 @@ + +export enum BlockType { + Container = 'interfaceContainerBlock', + DocumentsViewer = 'interfaceDocumentsSourceBlock', + Information = 'informationBlock', + PolicyRoles = 'policyRolesBlock', + Request = 'requestVcDocumentBlock', + SendToGuardian = 'sendToGuardianBlock', + Action = 'interfaceActionBlock', + Step = 'interfaceStepBlock', + Mint = 'mintDocumentBlock', + ExternalData = 'externalDataBlock', + AggregateDocument = 'aggregateDocumentBlock', + Wipe = 'retirementDocumentBlock', + FiltersAddon = 'filtersAddon', + DocumentsSourceAddon = 'documentsSourceAddon', + Calculate = 'calculateContainerBlock', + CalculateMathAddon = 'calculateMathAddon', + Report = 'reportBlock', + ReportItem = 'reportItemBlock', + ReassigningBlock = 'reassigningBlock', + PaginationAddon = 'paginationAddon', + TimerBlock = 'timerBlock', + CustomLogicBlock = 'customLogicBlock', + Switch = 'switchBlock', + RevokeBlock = 'revokeBlock', + SetRelationshipsBlock = 'setRelationshipsBlock', + ButtonBlock = 'buttonBlock', + TokenActionBlock = 'tokenActionBlock', + DocumentValidatorBlock = 'documentValidatorBlock', + TokenConfirmationBlock = 'tokenConfirmationBlock', + GroupManagerBlock = 'groupManagerBlock', + MultiSignBlock = 'multiSignBlock' +} diff --git a/frontend/src/app/policy-engine/structures/types/children-type.type.ts b/frontend/src/app/policy-engine/structures/types/children-type.type.ts new file mode 100644 index 0000000000..bd030cd69e --- /dev/null +++ b/frontend/src/app/policy-engine/structures/types/children-type.type.ts @@ -0,0 +1,6 @@ + +export enum ChildrenType { + None = 'None', + Special = 'Special', + Any = 'Any' +} diff --git a/frontend/src/app/policy-engine/structures/types/control-type.type.ts b/frontend/src/app/policy-engine/structures/types/control-type.type.ts new file mode 100644 index 0000000000..2b79359365 --- /dev/null +++ b/frontend/src/app/policy-engine/structures/types/control-type.type.ts @@ -0,0 +1,7 @@ + +export enum ControlType { + UI = 'UI', + Special = 'Special', + Server = 'Server', + None = 'None' +} diff --git a/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.css b/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.css new file mode 100644 index 0000000000..b718c15484 --- /dev/null +++ b/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.css @@ -0,0 +1,50 @@ +.text-editor { + border: 1px solid #e9e9e9; + min-height: 200px; + max-height: 200px; + overflow-y: scroll; + position: relative; + background: linear-gradient(to right, #f7f7f7 29px, #dddddd 29px 30px,white 30px 100% ); +} + +.enum-import-container { + display: flex; + justify-content: space-between; + gap: 10px; +} + +.enum-input-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.enum-import-url { + flex: 1; +} + +.enum-import-button { + margin-top: 5px; + min-width: 170px; +} + +.loading { + background: #fff; + z-index: 99; + margin: 0 auto; +} + +:host .g-dialog-actions-btn { + min-width: 170px !important; +} + +.enum-load-to-ipfs { + color: rgb(100 100 100); +} + +.enum-label { + color: var(--primary-color); + font-size: 20px; + font-size: 20px; + font-weight: 500; +} \ No newline at end of file diff --git a/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.html b/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.html new file mode 100644 index 0000000000..c8da84d303 --- /dev/null +++ b/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.html @@ -0,0 +1,40 @@ +
+
+
+
+ close +
+
+
+ Enum data +
+
+
+ OK +
+
+
+
+
+ + URL + + + + +
+
Enum
+ + Will data be loaded to IPFS? (There are more than 5 options) + +
+ +
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.ts b/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.ts new file mode 100644 index 0000000000..3a2c22407e --- /dev/null +++ b/frontend/src/app/schema-engine/enum-editor-dialog/enum-editor-dialog.component.ts @@ -0,0 +1,128 @@ +import { AfterContentInit, Component, Inject } from '@angular/core'; +import { FormControl, Validators } from '@angular/forms'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; + +/** + * Dialog allowing you to select a file and load schemas. + */ +@Component({ + selector: 'enum-editor-dialog', + templateUrl: './enum-editor-dialog.component.html', + styleUrls: ['./enum-editor-dialog.component.css'] +}) +export class EnumEditorDialog implements AfterContentInit { + enumValue!: string; + + codeMirrorOptions: any = { + lineNumbers: true, + theme: 'default', + mode: 'text/plain' + }; + + initDialog: boolean = false; + loading: boolean = false; + loadToIpfs: boolean = false; + loadToIpfsValue: boolean = true; + + code: FormControl = new FormControl(); + urlControl = new FormControl("", [ + Validators.pattern(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/) + ]); + + errorHandler!: Function; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { + enumValue: string[], + errorHandler: Function + } + ) { + this.enumValue = data.enumValue?.join('\n') || "FIRST_OPTION\nSECOND_OPTION\nTHIRD_OPTION"; + this.loadToIpfs = data.enumValue.length > 5; + this.errorHandler = data.errorHandler; + } + + ngOnInit() { + } + + onNoClick(): void { + this.dialogRef.close(null); + } + + ngAfterContentInit() { + setTimeout(() => { + this.initDialog = true; + + // Allows drop files into text-editor directly + const dropArea = document.getElementById('text-area'); + dropArea?.addEventListener('dragover', (event) => { + event.stopPropagation(); + event.preventDefault(); + }); + dropArea?.addEventListener('drop', (event) => { + event.stopPropagation(); + event.preventDefault(); + }); + }, 150); + } + + importEnumData(file: any) { + const reader = new FileReader() + reader.readAsText(file); + reader.addEventListener('load', (e: any) => { + const fileText = e.target.result; + this.dialogRef.close(new Blob([ + JSON.stringify({ + enum: [...new Set(fileText.split('\r\n'))] + }) + ])); + }); + } + + onImportByUrl() { + this.loading = true; + fetch(this.urlControl.value) + .then(res => res.text()) + .then((jsontext: any) => { + try { + const parsedText = JSON.parse(jsontext); + this.enumValue = parsedText && parsedText.enum?.join('\n') || ""; + } catch { + this.enumValue = jsontext; + } + }) + .catch((err) => { + if(this.errorHandler) { + this.errorHandler(err.message, 'Can not import by URL'); + } + }) + .finally(() => { this.loading = false }); + } + + onImportByFile() { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".txt"; + input.onchange = (e: any) => { + const file: File = e.target?.files[0]; + if (!file) { + return; + } + this.loading = true; + var reader = new FileReader(); + reader.readAsText(file, 'UTF-8'); + reader.onload = (readerEvent: any) => { + this.loading = false; + var content = readerEvent?.target?.result || ""; + this.enumValue = content; + } + } + input.click(); + } + + checkLoadIpfsVisible(value: string) { + const linesCount = (value?.match(/\n/g) || []).length; + this.loadToIpfs = linesCount > 4; + } +} diff --git a/frontend/src/app/schema-engine/field-control.ts b/frontend/src/app/schema-engine/field-control.ts index 2c647a33ff..7204f20ed7 100644 --- a/frontend/src/app/schema-engine/field-control.ts +++ b/frontend/src/app/schema-engine/field-control.ts @@ -1,4 +1,5 @@ import { + FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; @@ -21,6 +22,8 @@ export class FieldControl { public controlRequired: FormControl; public controlArray: FormControl; public controlUnit: FormControl; + public controlRemoteLink: FormControl; + public controlEnum: FormArray; private readonly _defaultFieldMap!: any; private _entityType!: FormControl; @@ -50,6 +53,11 @@ export class FieldControl { this.controlRequired = new FormControl(field.required); this.controlArray = new FormControl(field.isArray); this.controlUnit = new FormControl(field.unit); + this.controlRemoteLink = new FormControl(field.remoteLink); + this.controlEnum = new FormArray([]); + field.enum?.forEach(item => { + this.controlEnum.push(new FormControl(item)) + }); } else { this.controlKey = new FormControl(name || this.name, [ Validators.required, @@ -65,6 +73,8 @@ export class FieldControl { this.controlRequired = new FormControl(false); this.controlArray = new FormControl(false); this.controlUnit = new FormControl(''); + this.controlRemoteLink = new FormControl(''); + this.controlEnum = new FormArray([]); } this._entityType.valueChanges .pipe(takeUntil(destroyEvent)) @@ -114,6 +124,8 @@ export class FieldControl { fieldRequired: this.controlRequired, fieldArray: this.controlArray, fieldUnit: this.controlUnit, + controlRemoteLink: this.controlRemoteLink, + controlEnum: this.controlEnum }); } @@ -127,6 +139,8 @@ export class FieldControl { const required = group.fieldRequired; const isArray = group.fieldArray; const unit = group.fieldUnit; + const remoteLink = group.controlRemoteLink; + const enumArray = group.controlEnum; return { key, title, @@ -134,7 +148,9 @@ export class FieldControl { typeIndex, required, isArray, - unit + unit, + remoteLink, + enumArray }; } else { return null; diff --git a/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.css b/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.css index 8c9c88a7a9..f3930b090d 100644 --- a/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.css +++ b/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.css @@ -297,4 +297,35 @@ form { .conditions-container { margin-top: 10px; +} + +.re-order-handler { + position: absolute; + left: -40px; + top: 45%; + cursor: move; +} + +.custom-schema-field { + position: relative; +} + +.condition-container > .custom-fields { + padding-left: 25px; +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-preview { + background-color: white; + border-radius: 6px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } \ No newline at end of file diff --git a/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.html b/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.html index c8bfa77ee9..5f33c74790 100644 --- a/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.html +++ b/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.html @@ -37,12 +37,15 @@
- +
+
+ drag_handle +
- +
@@ -113,15 +116,19 @@

THEN

-
-
- - +
+
+
+ drag_handle +
+ - +
@@ -131,14 +138,19 @@

ELSE

-
-
- +
+
+
+
+ drag_handle +
- +
diff --git a/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.ts b/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.ts index 3cf9416bb2..8fe256944c 100644 --- a/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.ts +++ b/frontend/src/app/schema-engine/schema-configuration/schema-configuration.component.ts @@ -24,6 +24,8 @@ import { takeUntil } from 'rxjs/operators'; import { DATETIME_FORMATS } from '../schema-form/schema-form.component'; import { ConditionControl } from '../condition-control'; import { FieldControl } from '../field-control'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { IPFSService } from 'src/app/services/ipfs.service'; /** * Schemas constructor @@ -47,6 +49,7 @@ export class SchemaConfigurationComponent implements OnInit { @Input('extended') extended!: boolean; @Output('change-form') changeForm = new EventEmitter(); + @Output('change-fields') changeFields = new EventEmitter(); started = false; fields!: FieldControl[]; @@ -73,7 +76,8 @@ export class SchemaConfigurationComponent implements OnInit { }; constructor( - private fb: FormBuilder + private fb: FormBuilder, + private ipfs: IPFSService ) { this.defaultFieldsMap = {}; @@ -239,6 +243,7 @@ export class SchemaConfigurationComponent implements OnInit { }) } this.fields = []; + this.changeFields.emit(this.fields); this.conditions = []; } if (changes.value && this.value) { @@ -330,6 +335,7 @@ export class SchemaConfigurationComponent implements OnInit { updateFieldControls(fields: SchemaField[], conditionsFields: string[]) { this.fields = []; + this.changeFields.emit(this.fields); for (const field of fields) { if (field.readOnly || conditionsFields.find(elem => elem === field.name)) { continue; @@ -384,8 +390,13 @@ export class SchemaConfigurationComponent implements OnInit { for (let i = 0; i < keys.length; i++) { const key = keys[i]; const option = this.schemaTypeMap[key]; - if (field.customType && option.customType === field.customType) { - return key; + if (field.customType) { + if (option.customType === field.customType) { + return key; + } + else { + continue; + } } if (option.type === field.type) { @@ -462,11 +473,13 @@ export class SchemaConfigurationComponent implements OnInit { control.append(this.fieldsForm) this.fields.push(control); this.fields = this.fields.slice(); + this.changeFields.emit(this.fields); } onRemove(item: FieldControl) { this.removeConditionsByField(item); this.fields = this.fields.filter(e => e != item); + this.changeFields.emit(this.fields); item.remove(this.fieldsForm); } @@ -488,7 +501,9 @@ export class SchemaConfigurationComponent implements OnInit { typeIndex, required, isArray, - unit + unit, + remoteLink, + enumArray } = fieldConfig.getValue(data); const type = this.schemaTypeMap[typeIndex]; return { @@ -505,6 +520,12 @@ export class SchemaConfigurationComponent implements OnInit { unitSystem: type.unitSystem, customType: type.customType, readOnly: false, + remoteLink: type.customType === 'enum' + ? remoteLink + : undefined, + enum: type.customType === 'enum' && !remoteLink + ? enumArray + : undefined } } @@ -850,4 +871,8 @@ export class SchemaConfigurationComponent implements OnInit { return error; }; } + + drop(event: CdkDragDrop) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } } diff --git a/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.css b/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.css index 0bd26c183d..819830e6f1 100644 --- a/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.css +++ b/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.css @@ -22,4 +22,8 @@ textarea { min-width: 250px; text-align: center; cursor: pointer; +} + +:host ::ng-deep .cdk-drop-list-dragging *:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } \ No newline at end of file diff --git a/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.html b/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.html index 9547d27ab5..b4ae27ab56 100644 --- a/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.html +++ b/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.html @@ -27,11 +27,11 @@
-
+
+ (change-form)="onChangeForm($event)" (change-fields)="onChangeFields($event)">
diff --git a/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.ts b/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.ts index b3245eb5fa..15f32c27dc 100644 --- a/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.ts +++ b/frontend/src/app/schema-engine/schema-dialog/schema-dialog.component.ts @@ -1,8 +1,9 @@ -import { Component, Inject, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { FormBuilder } from '@angular/forms'; import { SchemaConfigurationComponent } from '../schema-configuration/schema-configuration.component'; import { Schema } from '@guardian/interfaces'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; /** * Dialog for creating and editing schemas. @@ -24,10 +25,12 @@ export class SchemaDialog { system: boolean = false; valid: boolean = true; extended: boolean = false; + fields: any[] = []; constructor( public dialogRef: MatDialogRef, private fb: FormBuilder, + private cdr: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: any) { this.schemasMap = data.schemasMap || {}; this.scheme = data.scheme || null; @@ -59,4 +62,13 @@ export class SchemaDialog { onChangeForm(schemaControl: SchemaConfigurationComponent) { this.valid = schemaControl.isValid(); } + + onChangeFields(fields: any[]) { + this.fields = fields; + this.cdr.detectChanges(); + } + + drop(event: CdkDragDrop) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } } diff --git a/frontend/src/app/schema-engine/schema-engine.module.ts b/frontend/src/app/schema-engine/schema-engine.module.ts index 4423d054ce..a37fcf138a 100644 --- a/frontend/src/app/schema-engine/schema-engine.module.ts +++ b/frontend/src/app/schema-engine/schema-engine.module.ts @@ -19,6 +19,8 @@ import { FileDragNDropComponent } from '../components/file-drag-n-drop/file-drag import { SchemaFieldConfigurationComponent } from './schema-field-configuration/schema-field-configuration.component'; import { SwitchButton } from '../components/switch-button/switch-button.component'; import { CommonComponentsModule } from '../common-components.module'; +import { EnumEditorDialog } from './enum-editor-dialog/enum-editor-dialog.component'; +import { CodemirrorModule } from '@ctrl/ngx-codemirror'; @NgModule({ declarations: [ @@ -34,7 +36,8 @@ import { CommonComponentsModule } from '../common-components.module'; ExportSchemaDialog, FileDragNDropComponent, SchemaFieldConfigurationComponent, - SwitchButton + SwitchButton, + EnumEditorDialog ], imports: [ CommonModule, @@ -45,7 +48,8 @@ import { CommonComponentsModule } from '../common-components.module'; NgxMatNativeDateModule, NgxMatTimepickerModule, ClipboardModule, - NgxFileDropModule + NgxFileDropModule, + CodemirrorModule ], exports: [ SchemaDialog, diff --git a/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.css b/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.css index 3f63a9d853..7de852ce18 100644 --- a/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.css +++ b/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.css @@ -181,4 +181,67 @@ .error-message { color: red; +} + +.enum-options-container { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + gap: 10px; + overflow: hidden; +} + +.enum-options-container .enum-form-field { + flex: 1; + pointer-events: none; +} + +.enum-options-container .enum-edit-button-only { + flex: 1; + padding: 15px 27px; +} + +.enum-edit-button { + color: var(--primary-color); + padding: 18px 27px; + border: 1px solid #e9e9e9; + border-radius: 6px; + margin-bottom: 11px; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + justify-content: center; +} + +.mat-chip-value-label { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 100%; +} + +.enum-form-field .mat-chip-value { + max-width: 95px; + min-width: 95px; +} + +:host ::ng-deep .enum-options-container .mat-form-field-infix { + padding: 10px 0; +} + +:host ::ng-deep .enum-form-field mat-chip-list .mat-chip-list-wrapper { + overflow-x: hidden; + flex-wrap: unset; +} + +.loading { + display: flex; + width: 100%; + justify-content: center; +} + +.enum-edit-button-label { + white-space: nowrap; } \ No newline at end of file diff --git a/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.html b/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.html index 10afb0d385..a4178f5278 100644 --- a/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.html +++ b/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.html @@ -78,4 +78,25 @@ Allow multiple answers
+
+ + Enum data + + +
{{keyword}}
+
+ + ... + +
+
+
+ edit + Edit values +
+
+
+ +
\ No newline at end of file diff --git a/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.ts b/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.ts index fe9c58fddd..4caaa146b4 100644 --- a/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.ts +++ b/frontend/src/app/schema-engine/schema-field-configuration/schema-field-configuration.component.ts @@ -7,11 +7,16 @@ import { Output } from '@angular/core'; import { - FormBuilder, FormControl, FormGroup, + Validators, } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; import { UnitSystem } from '@guardian/interfaces'; +import { ToastrService } from 'ngx-toastr'; +import { API_IPFS_GATEWAY_URL } from 'src/app/services/api'; +import { IPFSService } from 'src/app/services/ipfs.service'; +import { EnumEditorDialog } from '../enum-editor-dialog/enum-editor-dialog.component'; import { FieldControl } from "../field-control"; /** @@ -34,13 +39,60 @@ export class SchemaFieldConfigurationComponent implements OnInit { @Output('remove') remove = new EventEmitter(); unit: boolean = true; + enum: boolean = false; + loading: boolean = false; + keywords: string[] = []; - constructor(private fb: FormBuilder) { + constructor( + public dialog: MatDialog, + private ipfs: IPFSService, + private toastr: ToastrService + ) { } + ngOnInit(): void { + const enumValues = this.field.controlEnum.value + if (enumValues && enumValues.length) { + for (let i=0;i { + this.field.controlEnum.push(new FormControl(item)); + }); + + this.keywords = []; + if (values && values.length) { + for (let i = 0; i < values.length && i < 5; i++) { + this.keywords.push(values[i]); + } + } + } + loadRemoteEnumData(link:string) { + this.loading = true; + fetch(link) + .then(r=> r.json()) + .then((res: any) => { + if (!res) { + return; + } + this.updateControlEnum(res.enum); + }) + .catch((err) => this.errorHandler(err.message, 'Can not load remote enum data')) + .finally(() => this.loading = false); } ngOnChanges(changes: SimpleChanges): void { @@ -67,5 +119,68 @@ export class SchemaFieldConfigurationComponent implements OnInit { this.field.controlArray.enable(); } this.unit = event == UnitSystem.Prefix || event == UnitSystem.Postfix; + + this.enum = (item && item.name || event) === 'Enum'; + if (this.enum) { + this.field.controlEnum.setValidators([Validators.required]); + } else { + this.field.controlEnum.clearValidators(); + } + this.field.controlEnum.updateValueAndValidity(); + } + + onEditEnum() { + const dialogRef = this.dialog.open(EnumEditorDialog, { + panelClass: 'g-dialog', + width: "700px", + data: { + enumValue: this.field.controlEnum.value, + errorHandler: this.errorHandler.bind(this) + } + }); + dialogRef.afterClosed().subscribe((res: { enumValue: string, loadToIpfs: boolean }) => { + if (!res) { + return; + } + this.field.controlRemoteLink.patchValue(""); + + const uniqueTrimmedEnumValues: string[] = [ + ...new Set( + res.enumValue + .split('\n') + .map(item => item.trim()) + ) + ] as string[]; + + if (res.loadToIpfs && uniqueTrimmedEnumValues.length > 5) { + this.field.controlEnum.clear(); + this.loading = true; + this.ipfs.addFile(new Blob([ + JSON.stringify({ + enum: uniqueTrimmedEnumValues + }) + ])).subscribe(cid => { + this.loading = false; + const link = API_IPFS_GATEWAY_URL + cid; + this.field.controlRemoteLink.patchValue(link); + this.loadRemoteEnumData(link); + }, (err) => { + this.loading = false; + this.errorHandler(err.message, 'Enum data can not be loaded to IPFS'); + this.updateControlEnum(uniqueTrimmedEnumValues); + }); + } else { + this.updateControlEnum(uniqueTrimmedEnumValues); + } + }); + } + + private errorHandler(errorMessage: string, errorHeader: string): void { + this.toastr.error(errorMessage, errorHeader, { + timeOut: 30000, + closeButton: true, + positionClass: 'toast-bottom-right', + enableHtml: true + }); } } diff --git a/frontend/src/app/schema-engine/schema-form/schema-form.component.html b/frontend/src/app/schema-engine/schema-form/schema-form.component.html index 01f9326d6e..93d22a0642 100644 --- a/frontend/src/app/schema-engine/schema-form/schema-form.component.html +++ b/frontend/src/app/schema-engine/schema-form/schema-form.component.html @@ -64,6 +64,14 @@
+ + + + {{enumValue}} + + + +
{{item.unit}}
@@ -166,6 +174,14 @@
+ + + + {{enumValue}} + + + +
{{item.unit}}
diff --git a/frontend/src/app/schema-engine/schema-form/schema-form.component.ts b/frontend/src/app/schema-engine/schema-form/schema-form.component.ts index 54ac937519..288ba13fc3 100644 --- a/frontend/src/app/schema-engine/schema-form/schema-form.component.ts +++ b/frontend/src/app/schema-engine/schema-form/schema-form.component.ts @@ -189,7 +189,17 @@ export class SchemaFormComponent implements OnInit { if (!field.isArray && !field.isRef) { item.fileUploading = false; const validators = this.getValidators(item); - item.control = new FormControl(item.preset || "", validators); + item.control = new FormControl(item.preset === null || item.preset === undefined ? "" : item.preset, validators); + if (field.remoteLink) { + fetch(field.remoteLink) + .then(r => r.json()) + .then((res: any) => { + item.enumValues = res.enum; + }); + } + if (field.enum) { + item.enumValues = field.enum; + } this.postFormat(item, item.control); } @@ -204,6 +214,16 @@ export class SchemaFormComponent implements OnInit { if (field.isArray && !field.isRef) { item.control = new FormArray([]); item.list = []; + if (field.remoteLink) { + fetch(field.remoteLink) + .then(r => r.json()) + .then((res: any) => { + item.enumValues = res.enum; + }); + } + if (field.enum) { + item.enumValues = field.enum; + } if (item.preset && item.preset.length) { for (let index = 0; index < item.preset.length; index++) { const preset = item.preset[index]; @@ -260,7 +280,7 @@ export class SchemaFormComponent implements OnInit { } else { listItem.fileUploading = false; const validators = this.getValidators(item); - listItem.control = new FormControl(preset || "", validators); + listItem.control = new FormControl(preset === null || preset === undefined ? "" : preset, validators); this.postFormat(item, listItem.control); } @@ -280,7 +300,7 @@ export class SchemaFormComponent implements OnInit { } if (item.format === 'email') { - validators.push(Validators.pattern(/^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/)); + validators.push(Validators.email); } if (item.type === 'number') { @@ -558,10 +578,14 @@ export class SchemaFormComponent implements OnInit { item.format !== 'date' && item.format !== 'time' && item.format !== 'date-time' - ) + ) && !item.remoteLink && !item.enum ); } + isEnum(item: SchemaField) { + return item.remoteLink || item.enum; + } + isPrefix(item: SchemaField): boolean { return item.unitSystem === UnitSystem.Prefix; } @@ -569,4 +593,4 @@ export class SchemaFormComponent implements OnInit { isPostfix(item: SchemaField): boolean { return item.unitSystem === UnitSystem.Postfix; } -} \ No newline at end of file +} diff --git a/frontend/src/app/services/token.service.ts b/frontend/src/app/services/token.service.ts index e7013a669e..bc100942d1 100644 --- a/frontend/src/app/services/token.service.ts +++ b/frontend/src/app/services/token.service.ts @@ -24,7 +24,10 @@ export class TokenService { return this.http.post<{ taskId: string, expectation: number }>(`${this.url}/push/`, data); } - public getTokens(): Observable { + public getTokens(policyId?: string): Observable { + if (policyId) { + return this.http.get(`${this.url}?policy=${policyId}`); + } return this.http.get(`${this.url}`); } diff --git a/frontend/src/app/services/web-socket.service.ts b/frontend/src/app/services/web-socket.service.ts index 832e487d72..7f89ef0632 100644 --- a/frontend/src/app/services/web-socket.service.ts +++ b/frontend/src/app/services/web-socket.service.ts @@ -115,9 +115,8 @@ export class WebSocketService { this.reconnect(); } }); + this.send('SET_ACCESS_TOKEN', this.auth.getAccessToken()); this.heartbeat(); - await this.send('SET_ACCESS_TOKEN', this.auth.getAccessToken()); - await this.send(MessageAPI.GET_STATUS, null); } private heartbeat() { @@ -143,9 +142,9 @@ export class WebSocketService { this.sendingEvent = true; this.socket?.next(data); setTimeout(() => { - this.sendingEvent = false; - resolve(); - }, + this.sendingEvent = false; + resolve(); + }, 100 ); }) @@ -183,7 +182,8 @@ export class WebSocketService { this.updateStatus(event.data); const allStatesReady = !this.serviesStates.find((item: any) => item.state !== ApplicationStates.READY) if (!allStatesReady) { - this.router.navigate(['/status']); + const last = location.pathname === '/status' ? null : btoa(location.href); + this.router.navigate(['/status'], { queryParams: { last } }); } this.servicesReady.next(allStatesReady); break; diff --git a/frontend/src/app/views/admin/service-status/service-status.component.css b/frontend/src/app/views/admin/service-status/service-status.component.css index 94aa6d3006..c702460b1a 100644 --- a/frontend/src/app/views/admin/service-status/service-status.component.css +++ b/frontend/src/app/views/admin/service-status/service-status.component.css @@ -29,6 +29,7 @@ flex-direction: column; height: 60%; font-size: xx-large; + background: #fff; } .info-message { @@ -38,4 +39,14 @@ .a, a:visited { color: blue; +} + +.back { + color: rgb(0, 0, 238); + cursor: pointer; + text-decoration: underline; + font-size: 20px; + height: 20px; + padding: 6px; + margin-top: 20px; } \ No newline at end of file diff --git a/frontend/src/app/views/admin/service-status/service-status.component.html b/frontend/src/app/views/admin/service-status/service-status.component.html index 1ddd724a01..0ea7a23793 100644 --- a/frontend/src/app/views/admin/service-status/service-status.component.html +++ b/frontend/src/app/views/admin/service-status/service-status.component.html @@ -1,9 +1,11 @@
+ +

{{ getServiceNames(loadingServices) }} is (are) initializing -
+
Please wait...

@@ -12,34 +14,38 @@

All services are running

+
Disabled - +
Event Actor - + Event Initiator Document Owner Document Issuer @@ -392,7 +427,8 @@ Disabled - +
+ + expand_more + + + {{property.label}} +
+ + expand_more + + + {{property.label}} + +
+ add + Add {{property.items.label}} +
+
+ + expand_more + + + {{property.items.label}} {{i}} + + + delete + +
{{property.label}} + +
{{property.label}} + +
{{property.label}} + + {{item.label}} + +
Name - +
Policy Tag - +
Previous Version - +
Version - +
Topic Description - +
Role {{i}} - + delete @@ -128,7 +128,7 @@ Name - +
Name - +
Description - +
Static - +
Memo - +
+ + + + + +
+ {{ service.serviceName }} + - +
+ check + (Ready) +
+
+ + (Initializing) +
+
+ + (Started) +
+
+ close + + (Stopped, View + Logs) + +
+
+
+ Return to previous page +
- - - - - - -
- {{ service.serviceName }} - - -
- check - (Ready) -
-
- - (Initializing) -
-
- - (Started) -
-
- close - - (Stopped, View Logs) - -
-
-
+
\ No newline at end of file diff --git a/frontend/src/app/views/admin/service-status/service-status.component.ts b/frontend/src/app/views/admin/service-status/service-status.component.ts index f35cb29723..a9b1fb1ed0 100644 --- a/frontend/src/app/views/admin/service-status/service-status.component.ts +++ b/frontend/src/app/views/admin/service-status/service-status.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; import { ApplicationStates } from '@guardian/interfaces'; -import { Observable, of } from 'rxjs'; import { WebSocketService } from 'src/app/services/web-socket.service'; /** @@ -14,11 +14,22 @@ import { WebSocketService } from 'src/app/services/web-socket.service'; export class ServiceStatusComponent implements OnInit { servicesStates: any[] = []; + last?: any; constructor( - private wsService: WebSocketService + private wsService: WebSocketService, + private route: ActivatedRoute, + private router: Router ) { this.servicesStates = this.wsService.getServicesStatesArray(); + this.last = this.route?.snapshot?.queryParams?.last; + try { + if (this.last) { + this.last = atob(this.last); + } + } catch (error) { + this.last = null; + } } getLoadingServices() { @@ -33,5 +44,11 @@ export class ServiceStatusComponent implements OnInit { return serviceStates.map((item: any) => item.serviceName).join(', '); } - ngOnInit() { } + ngOnInit() { + + } + + onBack() { + window.location.href = this.last; + } } \ No newline at end of file diff --git a/frontend/src/app/views/admin/settings-view/settings-view.component.html b/frontend/src/app/views/admin/settings-view/settings-view.component.html index 51cff09d5d..84aeef3ca7 100644 --- a/frontend/src/app/views/admin/settings-view/settings-view.component.html +++ b/frontend/src/app/views/admin/settings-view/settings-view.component.html @@ -3,17 +3,17 @@

Change settings

OPERATOR ID: - + OPERATOR KEY: - + IPFS STORAGE API KEY: - + - -
- +
+ + Policy + + All policies + + {{policy.name}} + ({{policy.id}}) + + + +
+
+ +
+
@@ -50,7 +68,12 @@ @@ -112,6 +135,7 @@
- +
\ No newline at end of file diff --git a/frontend/src/app/views/token-config/token-config.component.ts b/frontend/src/app/views/token-config/token-config.component.ts index b9eb614b60..c2a8470e8d 100644 --- a/frontend/src/app/views/token-config/token-config.component.ts +++ b/frontend/src/app/views/token-config/token-config.component.ts @@ -8,6 +8,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Token } from '@guardian/interfaces'; import { InformService } from 'src/app/services/inform.service'; import { TasksService } from 'src/app/services/tasks.service'; +import { forkJoin } from 'rxjs'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; enum OperationMode { None, Create, Kyc @@ -47,6 +49,8 @@ export class TokenConfigComponent implements OnInit { expectedTaskMessages: number = 0; operationMode: OperationMode = OperationMode.None; user: any; + currentPolicy: any = ''; + policies: any[] | null = null; constructor( private auth: AuthService, @@ -54,6 +58,7 @@ export class TokenConfigComponent implements OnInit { private tokenService: TokenService, private informService: InformService, private taskService: TasksService, + private policyEngineService: PolicyEngineService, private route: ActivatedRoute, private router: Router, public dialog: MatDialog) { @@ -63,13 +68,27 @@ export class TokenConfigComponent implements OnInit { ngOnInit() { this.tokenId = ""; this.loading = true; + this.currentPolicy = this.route.snapshot.queryParams['policy']; this.route.queryParams.subscribe(queryParams => { this.loadProfile(); }); } + onFilter() { + if (this.currentPolicy) { + this.router.navigate(['/tokens'], { + queryParams: { + policy: this.currentPolicy + } + }); + } else { + this.router.navigate(['/tokens']); + } + this.loadTokens(); + } + loadTokens() { - this.tokenService.getTokens().subscribe((data: any) => { + this.tokenService.getTokens(this.currentPolicy).subscribe((data: any) => { this.tokens = data.map((e: any) => { return { ...new Token(e), @@ -108,8 +127,14 @@ export class TokenConfigComponent implements OnInit { loadProfile() { this.loading = true; - this.profileService.getProfile().subscribe((profile) => { + forkJoin([ + this.profileService.getProfile(), + this.policyEngineService.all(), + ]).subscribe((value) => { + const profile = value[0]; + const policies = value[1] || []; this.isConfirmed = !!(profile && profile.confirmed); + this.policies = policies; if (this.isConfirmed) { this.queryChange(); } else { diff --git a/frontend/src/app/views/user-profile/user-profile.component.ts b/frontend/src/app/views/user-profile/user-profile.component.ts index 0f707b23b9..edd03498bf 100644 --- a/frontend/src/app/views/user-profile/user-profile.component.ts +++ b/frontend/src/app/views/user-profile/user-profile.component.ts @@ -12,6 +12,7 @@ import { SchemaService } from 'src/app/services/schema.service'; import { HeaderPropsService } from 'src/app/services/header-props.service'; import { InformService } from 'src/app/services/inform.service'; import { TasksService } from 'src/app/services/tasks.service'; +import { WebSocketService } from 'src/app/services/web-socket.service'; enum OperationMode { None, Generate, SetProfile, Associate @@ -79,6 +80,7 @@ export class UserProfileComponent implements OnInit { private schemaService: SchemaService, private informService: InformService, private taskService: TasksService, + private webSocketService: WebSocketService, private fb: FormBuilder, public dialog: MatDialog, private headerProps: HeaderPropsService) { @@ -296,7 +298,7 @@ export class UserProfileComponent implements OnInit { switch (operationMode) { case OperationMode.Generate: this.taskService.get(taskId).subscribe((task) => { - const { id, key} = task.result; + const { id, key } = task.result; value.id = id; value.key = key; this.hederaForm.setValue(value); @@ -304,6 +306,9 @@ export class UserProfileComponent implements OnInit { }); break; case OperationMode.SetProfile: + this.webSocketService.updateProfile(); + this.loadDate(); + break; case OperationMode.Associate: this.loadDate(); break; diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 443766e520..44b6f93db6 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -4,6 +4,7 @@ @import '~codemirror/theme/material'; @import '~codemirror/addon/fold/foldgutter.css'; @import '~codemirror/addon/lint/lint.css'; +@import url("https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined"); html, body { @@ -162,4 +163,8 @@ button.mat-button { .mat-datepicker-content .mat-calendar { height: unset !important; +} + +.not-editable-text { + color: gray; } \ No newline at end of file diff --git a/guardian-service/package.json b/guardian-service/package.json index 656b9dd36a..2fe94bcbd2 100644 --- a/guardian-service/package.json +++ b/guardian-service/package.json @@ -12,8 +12,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", - "@guardian/interfaces": "^2.4.2", + "@guardian/common": "^2.5.0-prerelease", + "@guardian/interfaces": "^2.5.0-prerelease", "@hashgraph/sdk": "^2.15.0", "@mikro-orm/core": "^5.3.0", "@mikro-orm/mongodb": "^5.3.0", @@ -72,8 +72,8 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/guardian-service.xml", "test:local": "mocha tests/**/*.test.js", - "test:network": "mocha tests/network-tests/**/*.test.js", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/guardian-service/src/api/config.service.ts b/guardian-service/src/api/config.service.ts index 33f3de1da6..6e37ad8a31 100644 --- a/guardian-service/src/api/config.service.ts +++ b/guardian-service/src/api/config.service.ts @@ -6,7 +6,7 @@ import { MessageResponse, MessageError, Logger, - DataBaseHelper + DataBaseHelper, SettingsContainer } from '@guardian/common'; import { MessageAPI, CommonSettings } from '@guardian/interfaces'; import { Environment } from '@hedera-modules'; @@ -31,23 +31,12 @@ export async function configAPI( * Update settings * */ - ApiResponse(channel, MessageAPI.UPDATE_SETTINGS, async (msg) => { + ApiResponse(channel, MessageAPI.UPDATE_SETTINGS, async (settings: CommonSettings) => { try { - const settings = msg as CommonSettings; - const opId = { - name: 'OPERATOR_ID', - value: settings.operatorId - }; - await settingsRepository.save(opId, { - name: 'OPERATOR_ID' - }); - const opKey = { - name: 'OPERATOR_KEY', - value: settings.operatorKey - }; - await settingsRepository.save(opKey, { - name: 'OPERATOR_KEY' - }); + const settingsContainer = new SettingsContainer(); + await settingsContainer.updateSetting('OPERATOR_ID', settings.operatorId); + await settingsContainer.updateSetting('OPERATOR_KEY', settings.operatorKey) + return new MessageResponse(null); } catch (error) { @@ -61,15 +50,13 @@ export async function configAPI( */ ApiResponse(channel, MessageAPI.GET_SETTINGS, async (msg) => { try { - const operatorId = await settingsRepository.findOne({ - name: 'OPERATOR_ID' - }); - const operatorKey = await settingsRepository.findOne({ - name: 'OPERATOR_KEY' - }); + const settingsContainer = new SettingsContainer(); + const { OPERATOR_ID } = settingsContainer.settings; + return new MessageResponse({ - operatorId: operatorId?.value || process.env.OPERATOR_ID, - operatorKey: operatorKey?.value || process.env.OPERATOR_KEY + operatorId: OPERATOR_ID, + // operatorKey: OPERATOR_KEY + operatorKey: '' }); } catch (error) { diff --git a/guardian-service/src/api/demo.ts b/guardian-service/src/api/demo.ts index 07b982df75..9a4d377535 100644 --- a/guardian-service/src/api/demo.ts +++ b/guardian-service/src/api/demo.ts @@ -1,17 +1,18 @@ import { Settings } from '@entity/settings'; -import { HederaSDKHelper } from '@hedera-modules'; import { ApiResponse } from '@api/api-response'; import { Policy } from '@entity/policy'; import { + DataBaseHelper, + Logger, MessageBrokerChannel, - MessageResponse, MessageError, - Logger, - DataBaseHelper + MessageResponse, + SettingsContainer } from '@guardian/common'; -import { MessageAPI } from '@guardian/interfaces'; +import { MessageAPI, WorkerTaskType } from '@guardian/interfaces'; import { DatabaseServer } from '@database-modules'; import { emptyNotifier, initNotifier, INotifier } from '@helpers/notifier'; +import { Workers } from '@helpers/workers'; /** * Demo key @@ -35,14 +36,9 @@ interface DemoKey { */ async function generateDemoKey(role: any, settingsRepository: DataBaseHelper, notifier: INotifier): Promise { notifier.start('Resolve settings'); - const operatorId = await settingsRepository.findOne({ - name: 'OPERATOR_ID' - }); - const operatorKey = await settingsRepository.findOne({ - name: 'OPERATOR_KEY' - }); - const OPERATOR_ID = operatorId?.value || process.env.OPERATOR_ID; - const OPERATOR_KEY = operatorKey?.value || process.env.OPERATOR_KEY; + + const settingsContainer = new SettingsContainer(); + const {OPERATOR_ID, OPERATOR_KEY} = settingsContainer.settings; let initialBalance: number = null; try { if (role === 'STANDARD_REGISTRY') { @@ -54,13 +50,17 @@ async function generateDemoKey(role: any, settingsRepository: DataBaseHelper { } } -/** - * Update user balance - * @param channel - */ -export function updateUserBalance(channel: MessageBrokerChannel) { - return async (client: any) => { - try { - const balance = await HederaSDKHelper.balance(client, client.operatorAccountId); - const users = new Users(); - const user: any = await users.getUserByAccount(client.operatorAccountId.toString()); - await channel.request(['api-gateway', 'update-user-balance'].join('.'), { - balance, - unit: 'Hbar', - user: user ? { - username: user.username, - did: user.did - } : null - }); - } catch (error) { - await new Logger().info(error.message, ['GUARDIAN_SERVICE', 'TransactionResponse']); - } - } -} +// /** +// * Update user balance +// * @param channel +// */ +// export function updateUserBalance(channel: MessageBrokerChannel) { +// return async (client: any) => { +// try { +// const balance = await HederaSDKHelper.balance(client, client.operatorAccountId); +// const users = new Users(); +// const user: any = await users.getUserByAccount(client.operatorAccountId.toString()); +// await channel.request(['api-gateway', 'update-user-balance'].join('.'), { +// balance, +// unit: 'Hbar', +// user: user ? { +// username: user.username, +// did: user.did +// } : null +// }); +// } catch (error) { +// await new Logger().info(error.message, ['GUARDIAN_SERVICE', 'TransactionResponse']); +// } +// } +// } /** * Set up user profile @@ -345,6 +345,7 @@ export function profileAPI(channel: MessageBrokerChannel, apiGatewayChannel: Mes const { username } = msg; const wallet = new Wallet(); const users = new Users(); + const workers = new Workers(); const user = await users.getUser(username); if (!user) { @@ -356,8 +357,13 @@ export function profileAPI(channel: MessageBrokerChannel, apiGatewayChannel: Mes } const key = await wallet.getKey(user.walletToken, KeyType.KEY, user.did); - const client = HederaSDKHelper.client(user.hederaAccountId, key); - const balance = await HederaSDKHelper.balance(client, client.operatorAccountId); + const balance = await workers.addTask({ + type: WorkerTaskType.GET_USER_BALANCE, + data: { + hederaAccountId: user.hederaAccountId, + hederaAccountKey: key + } + }, 1); return new MessageResponse({ balance, unit: 'Hbar', @@ -379,6 +385,7 @@ export function profileAPI(channel: MessageBrokerChannel, apiGatewayChannel: Mes const wallet = new Wallet(); const users = new Users(); + const workers = new Workers(); const user = await users.getUser(username); @@ -391,8 +398,14 @@ export function profileAPI(channel: MessageBrokerChannel, apiGatewayChannel: Mes } const key = await wallet.getKey(user.walletToken, KeyType.KEY, user.did); - const client = new HederaSDKHelper(user.hederaAccountId, key); - const balance = await client.balance(user.hederaAccountId); + const balance = await workers.addTask({ + type: WorkerTaskType.GET_USER_BALANCE, + data: { + hederaAccountId: user.hederaAccountId, + hederaAccountKey: key + } + }, 1); + return new MessageResponse(balance); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); diff --git a/guardian-service/src/api/schema.service.ts b/guardian-service/src/api/schema.service.ts index 3ba3545346..0baa6a21ec 100644 --- a/guardian-service/src/api/schema.service.ts +++ b/guardian-service/src/api/schema.service.ts @@ -7,7 +7,10 @@ import { SchemaStatus, TopicType, SchemaHelper, - ModelHelper, GenerateUUIDv4, Schema, + ModelHelper, + GenerateUUIDv4, + Schema, + IRootConfig, } from '@guardian/interfaces'; import path from 'path'; import { readJSON } from 'fs-extra'; @@ -158,6 +161,17 @@ function onlyUnique(value: any, index: any, self: any): boolean { return self.indexOf(value) === index; } +/** + * Check circular dependency in schema + * @param schema Schema + * @returns Does circular dependency exists + */ +function checkForCircularDependency(schema: ISchema) { + return schema.document?.$defs && schema.document.$id + ? Object.keys(schema.document.$defs).includes(schema.document.$id) + : false; +} + /** * Increment schema version * @param iri @@ -200,6 +214,9 @@ export async function incrementSchemaVersion(iri: string, owner: string): Promis * @param owner */ async function createSchema(newSchema: ISchema, owner: string, notifier: INotifier): Promise { + if (checkForCircularDependency(newSchema)) { + throw new Error(`There is circular dependency in schema: ${newSchema.iri}`); + } delete newSchema.id; delete newSchema._id; const users = new Users(); @@ -298,19 +315,25 @@ export async function importSchemaByFiles(owner: string, files: ISchema[], topic } notifier.info(`Found ${files.length} schemas`); - let num: number = 0; - const createdSchemas = []; for (const file of files) { file.document = replaceValueRecursive(file.document, uuidMap); file.context = replaceValueRecursive(file.context, uuidMap); SchemaHelper.setVersion(file, '', ''); - createdSchemas.push(await createSchema(file, owner, emptyNotifier())); - num++; - notifier.info(`Schema ${num} (${file.name || '-'}) created`); } - for (const createdSchema of createdSchemas) { - await updateSchemaDocument(createdSchema, createdSchemas); + const parsedSchemas = files.map(item => new Schema(item, true)); + const updatedSchemasMap = {}; + for (const file of files) { + fixSchemaDefsOnImport(file.iri, parsedSchemas, updatedSchemasMap); + } + + let num: number = 0; + for (const file of files) { + const parsedSchema = updatedSchemasMap[file.iri]; + file.document = parsedSchema.document; + await createSchema(file, owner, emptyNotifier()); + num++; + notifier.info(`Schema ${num} (${file.name || '-'}) created`); } const schemasMap = []; @@ -339,6 +362,9 @@ export async function publishSchema( messageServer: MessageServer, type?: MessageAction ): Promise { + if (checkForCircularDependency(item)) { + throw new Error(`There is circular dependency in schema: ${item.iri}`); + } const itemDocument = item.document; const defsArray = itemDocument.$defs ? Object.values(itemDocument.$defs) : []; item.context = schemasToContext([...defsArray, itemDocument]); @@ -404,6 +430,8 @@ async function updateSchemaDefs(schemaId: string, oldSchemaId?: string) { if (!schemaDocument) { return; } + + const schemaDefs = schema.document.$defs; delete schemaDocument.$defs; const filters = {}; @@ -416,6 +444,11 @@ async function updateSchemaDefs(schemaId: string, oldSchemaId?: string) { rSchema.document = JSON.parse(document); } rSchema.document.$defs[schemaId] = schemaDocument; + if (schemaDefs) { + for (const def of Object.keys(schemaDefs)) { + rSchema.document.$defs[def] = schemaDefs[def]; + } + } } await DatabaseServer.updateSchemas(relatedSchemas); } @@ -424,11 +457,11 @@ async function updateSchemaDefs(schemaId: string, oldSchemaId?: string) { * Update schema document * @param schemaId Schema Identifier */ -async function updateSchemaDocument(schema: SchemaCollection, allSchemas?: SchemaCollection[]): Promise { +async function updateSchemaDocument(schema: SchemaCollection): Promise { if (!schema) { throw new Error(`There is no schema to update document`); } - const allSchemasInTopic = allSchemas || await DatabaseServer.getSchemas({ + const allSchemasInTopic = await DatabaseServer.getSchemas({ topicId: schema.topicId, }); @@ -440,13 +473,38 @@ async function updateSchemaDocument(schema: SchemaCollection, allSchemas?: Schem await DatabaseServer.updateSchema(schema.id, schema); } +/** + * Fixing defs in importing schemas + * @param iri Schema iri + * @param schemas Schemas + * @param map Map of updated schemas + */ +function fixSchemaDefsOnImport(iri: string, schemas: Schema[], map: any) { + if (map[iri]) { + return; + } + const schema = schemas.find(s => s.iri === iri); + if (!schema) { + throw new Error(`Schema ${schema.iri} doesn't exist`); + } + for (const field of schema.fields) { + if (field.isRef) { + fixSchemaDefsOnImport(field.type, schemas, map); + } + } + schema.update(schema.fields, schema.conditions); + schema.updateRefs(schemas); + map[iri] = schema; +} + /** * Publishing schemas in defs * @param defs Definitions * @param version Version * @param owner Owner + * @param root HederaAccount */ -export async function publishDefsSchemas(defs: any, version: string, owner: string) { +export async function publishDefsSchemas(defs: any, owner: string, root: IRootConfig) { if (!defs) { return; } @@ -457,8 +515,9 @@ export async function publishDefsSchemas(defs: any, version: string, owner: stri 'document.$id': schemaId }); if (schema && schema.status !== SchemaStatus.PUBLISHED) { + console.log(schema.iri, schema.owner, owner); schema = await incrementSchemaVersion(schema.iri, owner); - await findAndPublishSchema(schema.id, schema.version, owner, emptyNotifier()); + await findAndPublishSchema(schema.id, schema.version, owner, root, emptyNotifier()); } } } @@ -468,9 +527,16 @@ export async function publishDefsSchemas(defs: any, version: string, owner: stri * @param id * @param version * @param owner + * @param root * @param notifier */ -export async function findAndPublishSchema(id: string, version: string, owner: string, notifier: INotifier): Promise { +export async function findAndPublishSchema( + id: string, + version: string, + owner: string, + root: IRootConfig, + notifier: INotifier +): Promise { notifier.start('Load schema'); let item = await DatabaseServer.getSchema(id); @@ -489,12 +555,9 @@ export async function findAndPublishSchema(id: string, version: string, owner: s notifier.completedAndStart('Publishing related schemas'); const oldSchemaId = item.document?.$id; - await publishDefsSchemas(item.document?.$defs, version, owner); + await publishDefsSchemas(item.document?.$defs, owner, root); item = await DatabaseServer.getSchema(id); - notifier.completedAndStart('Resolve Hedera account'); - const users = new Users(); - const root = await users.getHederaAccount(owner); notifier.completedAndStart('Resolve topic'); const topic = await DatabaseServer.getTopicById(item.topicId); const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey) @@ -663,7 +726,7 @@ export async function schemaAPI(channel: MessageBrokerChannel, apiGatewayChannel const schemaObject = msg as ISchema; SchemaHelper.setVersion(schemaObject, null, schemaObject.version); await createSchema(schemaObject, schemaObject.owner, emptyNotifier()); - const schemas = await DatabaseServer.getSchemas(); + const schemas = await DatabaseServer.getSchemas(null, { limit: 100 }); return new MessageResponse(schemas); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -700,6 +763,9 @@ export async function schemaAPI(channel: MessageBrokerChannel, apiGatewayChannel const id = msg.id as string; const item = await DatabaseServer.getSchema(id); if (item) { + if (checkForCircularDependency(item)) { + throw new Error(`There is circular dependency in schema: ${item.iri}`); + } item.name = msg.name; item.description = msg.description; item.entity = msg.entity; @@ -710,7 +776,7 @@ export async function schemaAPI(channel: MessageBrokerChannel, apiGatewayChannel await DatabaseServer.updateSchema(item.id, item); await updateSchemaDefs(item.document.$id); } - const schemas = await DatabaseServer.getSchemas(); + const schemas = await DatabaseServer.getSchemas(null, { limit: 100 }); return new MessageResponse(schemas); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -817,7 +883,9 @@ export async function schemaAPI(channel: MessageBrokerChannel, apiGatewayChannel } const { id, version, owner } = msg; - const item = await findAndPublishSchema(id, version, owner, emptyNotifier()); + const users = new Users(); + const root = await users.getHederaAccount(owner); + const item = await findAndPublishSchema(id, version, owner, root, emptyNotifier()); return new MessageResponse(item); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -835,7 +903,10 @@ export async function schemaAPI(channel: MessageBrokerChannel, apiGatewayChannel notifier.error('Invalid id'); } - const item = await findAndPublishSchema(id, version, owner, notifier); + notifier.completedAndStart('Resolve Hedera account'); + const users = new Users(); + const root = await users.getHederaAccount(owner); + const item = await findAndPublishSchema(id, version, owner, root, notifier); notifier.result(item.id); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -858,7 +929,7 @@ export async function schemaAPI(channel: MessageBrokerChannel, apiGatewayChannel if (msg && msg.id) { await deleteSchema(msg.id, emptyNotifier()); } - const schemas = await DatabaseServer.getSchemas(); + const schemas = await DatabaseServer.getSchemas(null, { limit: 100 }); return new MessageResponse(schemas); } catch (error) { return new MessageError(error); diff --git a/guardian-service/src/api/token.service.ts b/guardian-service/src/api/token.service.ts index c5397ed64d..75286b7496 100644 --- a/guardian-service/src/api/token.service.ts +++ b/guardian-service/src/api/token.service.ts @@ -1,11 +1,12 @@ import { Token } from '@entity/token'; import { KeyType, Wallet } from '@helpers/wallet'; import { Users } from '@helpers/users'; -import { HederaSDKHelper } from '@hedera-modules'; +// import { HederaSDKHelper } from '@hedera-modules'; import { ApiResponse } from '@api/api-response'; import { MessageBrokerChannel, MessageResponse, MessageError, Logger, DataBaseHelper } from '@guardian/common'; -import { MessageAPI, IToken } from '@guardian/interfaces'; +import { MessageAPI, IToken, WorkerTaskType } from '@guardian/interfaces'; import { emptyNotifier, initNotifier, INotifier } from '@helpers/notifier'; +import { Workers } from '@helpers/workers'; /** * Get token info @@ -81,48 +82,29 @@ async function createToken(token: any, owner: any, tokenRepository: DataBaseHelp const root = await users.getHederaAccount(owner); notifier.completedAndStart('Create token'); - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey); - const treasury = client.newTreasury(root.hederaAccountId, root.hederaAccountKey); - const treasuryId = treasury.id; - const treasuryKey = treasury.key; - const adminKey = enableAdmin ? treasuryKey : null; - const kycKey = enableKYC ? treasuryKey : null; - const freezeKey = enableFreeze ? treasuryKey : null; - const wipeKey = enableWipe ? treasuryKey : null; - const supplyKey = changeSupply ? treasuryKey : null; - const nft = tokenType === 'non-fungible'; - const _decimals = nft ? 0 : decimals; - const _initialSupply = nft ? 0 : initialSupply; - const tokenId = await client.newToken( - tokenName, - tokenSymbol, - nft, - _decimals, - _initialSupply, - '', - treasury, - adminKey, - kycKey, - freezeKey, - wipeKey, - supplyKey, - ); + + const workers = new Workers(); + const tokenData = await workers.addTask({ + type: WorkerTaskType.CREATE_TOKEN, + data: { + operatorId: root.hederaAccountId, + operatorKey: root.hederaAccountKey, + changeSupply, + decimals, + enableAdmin, + enableFreeze, + enableKYC, + enableWipe, + initialSupply, + tokenName, + tokenSymbol, + tokenType + } + }, 1); + tokenData.owner = root.did; + notifier.completedAndStart('Save token in DB'); - const tokenObject = tokenRepository.create({ - tokenId, - tokenName, - tokenSymbol, - tokenType, - decimals: _decimals, - initialSupply: _initialSupply, - adminId: treasuryId ? treasuryId.toString() : null, - adminKey: adminKey ? adminKey.toString() : null, - kycKey: kycKey ? kycKey.toString() : null, - freezeKey: freezeKey ? freezeKey.toString() : null, - wipeKey: wipeKey ? wipeKey.toString() : null, - supplyKey: supplyKey ? supplyKey.toString() : null, - owner: root.did - }); + const tokenObject = tokenRepository.create(tokenData); const result = await tokenRepository.save(tokenObject); notifier.completed(); return result; @@ -159,13 +141,17 @@ async function associateToken(tokenId: any, did: any, associate: any, tokenRepos } notifier.completedAndStart(associate ? 'Associate' : 'Dissociate'); - const client = new HederaSDKHelper(userID, userKey); - let status: boolean; - if (associate) { - status = await client.associate(tokenId, userID, userKey); - } else { - status = await client.dissociate(tokenId, userID, userKey); - } + + const workers = new Workers(); + const status = await workers.addTask({ + type: WorkerTaskType.ASSOCIATE_TOKEN, + data: { + tokenId, + userID, + userKey, + associate + } + }, 1); notifier.completed(); return status; @@ -198,16 +184,30 @@ async function grantKycToken(tokenId, username, owner, grant, tokenRepository: D } const root = await users.getHederaAccount(owner); - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey); - const kycKey = token.kycKey; + notifier.completedAndStart(grant ? 'Grant KYC' : 'Revoke KYC'); - if (grant) { - await client.grantKyc(tokenId, user.hederaAccountId, kycKey); - } else { - await client.revokeKyc(tokenId, user.hederaAccountId, kycKey); - } + const workers = new Workers(); + await workers.addTask({ + type: WorkerTaskType.GRANT_KYC_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + userHederaAccountId: user.hederaAccountId, + tokenId, + kycKey: token.kycKey, + grant + } + }, 20); + + const info = await workers.addTask({ + type: WorkerTaskType.GET_ACCOUNT_INFO, + data: { + userID: root.hederaAccountId, + userKey: root.hederaAccountKey, + hederaAccountId: user.hederaAccountId, + } + }, 20); - const info = await client.accountInfo(user.hederaAccountId); const result = getTokenInfo(info, { tokenId }); notifier.completed(); return result; @@ -289,15 +289,28 @@ export async function tokenAPI( } const root = await users.getHederaAccount(owner); - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey); - const freezeKey = token.freezeKey; - if (freeze) { - await client.freeze(tokenId, user.hederaAccountId, freezeKey); - } else { - await client.unfreeze(tokenId, user.hederaAccountId, freezeKey); - } - const info = await client.accountInfo(user.hederaAccountId); + const workers = new Workers(); + await workers.addTask({ + type: WorkerTaskType.FREEZE_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + freezeKey: token.freezeKey, + tokenId, + freeze + } + }, 1); + + const info = await workers.addTask({ + type: WorkerTaskType.GET_ACCOUNT_INFO, + data: { + userID: root.hederaAccountId, + userKey: root.hederaAccountKey, + hederaAccountId: user.hederaAccountId, + } + }, 20); + const result = getTokenInfo(info, { tokenId }); return new MessageResponse(result); } catch (error) { @@ -382,8 +395,16 @@ export async function tokenAPI( } const root = await users.getHederaAccount(owner); - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey); - const info = await client.accountInfo(user.hederaAccountId); + const workers = new Workers(); + const info = await workers.addTask({ + type: WorkerTaskType.GET_ACCOUNT_INFO, + data: { + userID: root.hederaAccountId, + userKey: root.hederaAccountKey, + hederaAccountId: user.hederaAccountId + } + }, 1); + const result = getTokenInfo(info, token); return new MessageResponse(result); @@ -412,8 +433,16 @@ export async function tokenAPI( } - const client = new HederaSDKHelper(userID, userKey); - const info = await client.accountInfo(user.hederaAccountId); + const workers = new Workers(); + const info = await workers.addTask({ + type: WorkerTaskType.GET_ACCOUNT_INFO, + data: { + userID, + userKey, + hederaAccountId: user.hederaAccountId + } + }, 1); + const tokens: any = await tokenRepository.find(user.parent ? { where: { @@ -453,7 +482,6 @@ export async function tokenAPI( reqObj.where.tokenId = { $eq: msg.tokenId } const tokens = await tokenRepository.find(reqObj); return new MessageResponse(tokens); - } if (msg.ids) { const reqObj: any = { where: {} as unknown }; diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index 842e5eebca..284d73d6b3 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -1,7 +1,7 @@ import { configAPI } from '@api/config.service'; import { documentsAPI } from '@api/documents.service'; import { loaderAPI } from '@api/loader.service'; -import { profileAPI, updateUserBalance } from '@api/profile.service'; +import { profileAPI } from '@api/profile.service'; import { schemaAPI, setDefaultSchema } from '@api/schema.service'; import { tokenAPI } from '@api/token.service'; import { trustChainAPI } from '@api/trust-chain.service'; @@ -26,12 +26,11 @@ import { DataBaseHelper, DB_DI, Migration, - COMMON_CONNECTION_CONFIG + COMMON_CONNECTION_CONFIG, SettingsContainer, MessageResponse } from '@guardian/common'; -import { ApplicationStates } from '@guardian/interfaces'; +import { ApplicationStates, WorkerTaskType } from '@guardian/interfaces'; import { Environment, - HederaSDKHelper, MessageServer, TopicMemo, TransactionLogger, @@ -69,24 +68,21 @@ Promise.all([ new Logger().setChannel(channel); const state = new ApplicationState('GUARDIAN_SERVICE'); state.setChannel(channel); + const settingsContainer = new SettingsContainer(); + settingsContainer.setChannel(channel); + await settingsContainer.init('OPERATOR_ID', 'OPERATOR_KEY'); + + const {OPERATOR_ID, OPERATOR_KEY} = settingsContainer.settings; // Check configuration - if (!process.env.OPERATOR_ID || process.env.OPERATOR_ID.length < 5) { - await new Logger().error('You need to fill OPERATOR_ID field in .env file', ['GUARDIAN_SERVICE']); - throw new Error('You need to fill OPERATOR_ID field in .env file'); - } - if (!process.env.OPERATOR_KEY || process.env.OPERATOR_KEY.length < 5) { - await new Logger().error('You need to fill OPERATOR_KEY field in .env file', ['GUARDIAN_SERVICE']); - throw new Error('You need to fill OPERATOR_KEY field in .env file'); - } try { - AccountId.fromString(process.env.OPERATOR_ID); + AccountId.fromString(OPERATOR_ID); } catch (error) { - await new Logger().error('OPERATOR_ID field in .env file: ' + error.message, ['GUARDIAN_SERVICE']); - throw new Error('OPERATOR_ID field in .env file: ' + error.message); + await new Logger().error('OPERATOR_ID field in settings: ' + error.message, ['GUARDIAN_SERVICE']); + throw new Error('OPERATOR_ID field in settings: ' + error.message); } try { - PrivateKey.fromString(process.env.OPERATOR_KEY); + PrivateKey.fromString(OPERATOR_KEY); } catch (error) { await new Logger().error('OPERATOR_KEY field in .env file: ' + error.message, ['GUARDIAN_SERVICE']); throw new Error('OPERATOR_KEY field in .env file: ' + error.message); @@ -115,6 +111,7 @@ Promise.all([ Environment.setLocalNodeAddress(process.env.LOCALNODE_ADDRESS); Environment.setNetwork(process.env.HEDERA_NET); MessageServer.setLang(process.env.MESSAGE_LANG); + TransactionLogger.setLogLevel(process.env.LOG_LEVEL as TransactionLogLvl); TransactionLogger.setLogFunction((types: string[], date: string, duration: string, name: string, attr?: string[]) => { const log = new Logger(); @@ -139,13 +136,42 @@ Promise.all([ await DatabaseServer.setVirtualTransaction(id, type, operatorId); }); - HederaSDKHelper.setTransactionResponseCallback(updateUserBalance(channel)); - - if (!process.env.INITIALIZATION_TOPIC_ID && process.env.HEDERA_NET === 'localnode') { - const client = new HederaSDKHelper(process.env.OPERATOR_ID, process.env.OPERATOR_KEY); - const topicId = await client.newTopic(process.env.OPERATOR_KEY, null, TopicMemo.getGlobalTopicMemo()); - process.env.INITIALIZATION_TOPIC_ID = topicId; - } + channel.response('guardians.transaction-log-event', async (data: any) => { + console.log(data); + + setImmediate(async () => { + switch (data.type) { + case 'start-log': { + const {id, operatorAccountId, transactionName} = data.data; + await TransactionLogger.transactionLog(id, operatorAccountId, transactionName); + break; + } + + case 'end-log': { + const {id, operatorAccountId, transactionName, transaction, metadata} = data.data; + await TransactionLogger.transactionLog(id, operatorAccountId, transactionName, transaction, metadata); + break; + } + + case 'error-log': { + const {id, operatorAccountId, transactionName, transaction, error} = data.data; + await TransactionLogger.transactionErrorLog(id, operatorAccountId, transactionName, transaction, error); + break; + } + + case 'virtual-function-log': { + const {id, operatorAccountId, type} = data.data; + await TransactionLogger.virtualTransactionLog(id, type, operatorAccountId); + break; + } + + default: + throw new Error('Unknown transaction log event type'); + } + }) + + return new MessageResponse({}); + }); IPFS.setChannel(channel); new ExternalEventChannel().setChannel(channel); @@ -156,6 +182,18 @@ Promise.all([ workersHelper.setChannel(channel); workersHelper.initListeners(); + if (!process.env.INITIALIZATION_TOPIC_ID && process.env.HEDERA_NET === 'localnode') { + process.env.INITIALIZATION_TOPIC_ID = await workersHelper.addTask({ + type: WorkerTaskType.NEW_TOPIC, + data: { + hederaAccountId: OPERATOR_ID, + hederaAccountKey: OPERATOR_KEY, + dryRun: false, + topicMemo: TopicMemo.getGlobalTopicMemo() + } + }, 1); + } + const policyGenerator = new BlockTreeGenerator(); const policyService = new PolicyEngineService(channel, apiGatewayChannel); await policyGenerator.init(); diff --git a/guardian-service/src/database-modules/database-server.ts b/guardian-service/src/database-modules/database-server.ts index b562b1bf24..d0a6cbae96 100644 --- a/guardian-service/src/database-modules/database-server.ts +++ b/guardian-service/src/database-modules/database-server.ts @@ -11,9 +11,10 @@ import { Token as TokenCollection } from '@entity/token'; import { Topic as TopicCollection } from '@entity/topic'; import { DryRun } from '@entity/dry-run'; import { PolicyRoles as PolicyRolesCollection } from '@entity/policy-roles'; -import { DocumentStatus, SchemaEntity, TopicType } from '@guardian/interfaces'; +import { DocumentStatus, IVC, SchemaEntity, TopicType } from '@guardian/interfaces'; import { BaseEntity, DataBaseHelper } from '@guardian/common'; import { PolicyInvitations } from '@entity/policy-invitations'; +import { MultiDocuments } from '@entity/multi-documents'; /** * Database server @@ -48,6 +49,7 @@ export class DatabaseServer { this.classMap.set(DryRun, 'DryRun'); this.classMap.set(PolicyRolesCollection, 'PolicyRolesCollection'); this.classMap.set(PolicyInvitations, 'PolicyInvitations'); + this.classMap.set(MultiDocuments, 'MultiDocuments'); } /** @@ -158,6 +160,14 @@ export class DatabaseServer { */ private async aggregate(entityClass: new () => T, aggregation: any[]): Promise { if (this.dryRun) { + if (Array.isArray(aggregation)) { + aggregation.push({ + $match: { + dryRunId: this.dryRun, + dryRunClass: this.classMap.get(entityClass) + } + }) + } return await new DataBaseHelper(DryRun).aggregate(aggregation); } else { return await new DataBaseHelper(entityClass).aggregate(aggregation); @@ -383,6 +393,7 @@ export class DatabaseServer { item.owner = row.owner; item.group = row.group; item.assignedTo = row.assignedTo; + item.assignedToGroup = row.assignedToGroup; item.option = row.option; item.schema = row.schema; item.hederaStatus = row.hederaStatus; @@ -513,6 +524,18 @@ export class DatabaseServer { await this.remove(AggregateVC, removeMsp); } + /** + * Remove Aggregate Document + * @param hash + * @param blockId + * + * @virtual + */ + public async removeAggregateDocument(hash: string, blockId: string): Promise { + const item = await this.find(AggregateVC, { blockId, hash }); + await this.remove(AggregateVC, item); + } + /** * Create Aggregate Documents * @param item @@ -838,6 +861,21 @@ export class DatabaseServer { return await this.findOne(PolicyRolesCollection, { policyId, did, uuid }); } + /** + * Check User In Group + * @param group + * + * @virtual + */ + public async checkUserInGroup(group: any): Promise { + return await this.findOne(PolicyRolesCollection, { + policyId: group.policyId, + did: group.did, + owner: group.owner, + uuid: group.uuid + }); + } + /** * Get Groups By User * @param policyId @@ -853,6 +891,20 @@ export class DatabaseServer { return await this.find(PolicyRolesCollection, { policyId, did }, options); } + /** + * Get Active Group By User + * @param policyId + * @param did + * + * @virtual + */ + public async getActiveGroupByUser(policyId: string, did: string): Promise { + if (!did) { + return null; + } + return await this.findOne(PolicyRolesCollection, { policyId, did, active: true }); + } + /** * Get members * @@ -880,6 +932,16 @@ export class DatabaseServer { return await this.find(PolicyRolesCollection, { policyId, active: true }); } + /** + * Get all policy users + * @param policyId + * + * @virtual + */ + public async getAllUsersByRole(policyId: string, uuid: string, role: string): Promise { + return await this.find(PolicyRolesCollection, { policyId, uuid, role }); + } + /** * Delete user * @param group @@ -927,6 +989,128 @@ export class DatabaseServer { } } + /** + * Get MultiSign Status by document or user + * @param uuid + * @param documentId + * @param userId + * + * @virtual + */ + public async getMultiSignStatus(uuid: string, documentId: string, userId: string = 'Group'): Promise { + return await this.findOne(MultiDocuments, { uuid, documentId, userId }); + } + + /** + * Get MultiSign Statuses + * @param uuid + * @param documentId + * @param group + * + * @virtual + */ + public async getMultiSignDocuments(uuid: string, documentId: string, group: string): Promise { + return await this.find(MultiDocuments, { + where: { + uuid: { $eq: uuid }, + documentId: { $eq: documentId }, + group: { $eq: group }, + userId: { $ne: 'Group' } + } + }); + } + + /** + * Get MultiSign Statuses by group + * @param uuid + * @param group + * + * @virtual + */ + public async getMultiSignDocumentsByGroup(uuid: string, group: string): Promise { + return await this.find(MultiDocuments, { + where: { + uuid: { $eq: uuid }, + group: { $eq: group }, + userId: { $eq: 'Group' }, + status: { $eq: 'NEW' } + } + }); + } + + /** + * Set MultiSign Status by document + * @param uuid + * @param documentId + * @param group + * @param status + * + * @virtual + */ + public async setMultiSigStatus( + uuid: string, + documentId: string, + group: string, + status: string + ): Promise { + let item = await this.findOne(MultiDocuments, { + where: { + uuid: { $eq: uuid }, + documentId: { $eq: documentId }, + group: { $eq: group }, + userId: { $eq: 'Group' } + } + }); + if (item) { + item.status = status; + await this.update(MultiDocuments, item.id, item); + } else { + item = this.create(MultiDocuments, { + uuid, + documentId, + status, + document: null, + userId: 'Group', + did: null, + group, + username: null + }); + await this.save(MultiDocuments, item); + } + return item; + } + + /** + * Set MultiSign Status by user + * @param uuid + * @param documentId + * @param user + * @param status + * @param document + * + * @virtual + */ + public async setMultiSigDocument( + uuid: string, + documentId: string, + user: any, + status: string, + document: IVC + ): Promise { + const doc = this.create(MultiDocuments, { + uuid, + documentId, + status, + document, + userId: user.id, + did: user.did, + group: user.group, + username: user.username + }); + await this.save(MultiDocuments, doc); + return doc; + } + //Static /** @@ -958,8 +1142,8 @@ export class DatabaseServer { * Get schemas * @param filters */ - public static async getSchemas(filters?: any): Promise { - return await new DataBaseHelper(SchemaCollection).find(filters); + public static async getSchemas(filters?: any, options?: any): Promise { + return await new DataBaseHelper(SchemaCollection).find(filters, options); } /** diff --git a/guardian-service/src/entity/aggregate-documents.ts b/guardian-service/src/entity/aggregate-documents.ts index 6a4fa88e57..d0a8b528c8 100644 --- a/guardian-service/src/entity/aggregate-documents.ts +++ b/guardian-service/src/entity/aggregate-documents.ts @@ -19,6 +19,12 @@ export class AggregateVC extends BaseEntity { @Property({ nullable: true }) assignedTo?: string; + /** + * Document assign + */ + @Property({ nullable: true }) + assignedToGroup?: string; + /** * Document hash */ diff --git a/guardian-service/src/entity/dry-run.ts b/guardian-service/src/entity/dry-run.ts index 1b4006df68..62faf4a25f 100644 --- a/guardian-service/src/entity/dry-run.ts +++ b/guardian-service/src/entity/dry-run.ts @@ -127,6 +127,12 @@ export class DryRun extends BaseEntity { @Property({ nullable: true }) assignedTo?: any; + /** + * Assign + */ + @Property({ nullable: true }) + assignedToGroup?: string; + /** * Document hedera status */ @@ -432,6 +438,12 @@ export class DryRun extends BaseEntity { @Property({ nullable: true }) username?: string; + /** + * User Id + */ + @Property({ nullable: true }) + userId?: string; + /** * hederaAccountId */ diff --git a/guardian-service/src/entity/multi-documents.ts b/guardian-service/src/entity/multi-documents.ts new file mode 100644 index 0000000000..348294071d --- /dev/null +++ b/guardian-service/src/entity/multi-documents.ts @@ -0,0 +1,63 @@ +import { Entity, Property } from '@mikro-orm/core'; +import { BaseEntity } from '@guardian/common'; +import { IVC } from '@guardian/interfaces'; + +/** + * MultiDocuments collection + */ +@Entity() +export class MultiDocuments extends BaseEntity { + /** + * Block UUID + */ + @Property({ nullable: true }) + uuid?: string; + + /** + * Document Id + */ + @Property({ nullable: true }) + documentId?: string; + + /** + * User Id + */ + @Property({ nullable: true }) + userId?: string; + + /** + * (User DID) + */ + @Property({ nullable: true }) + did?: string; + + /** + * username + */ + @Property({ nullable: true }) + username?: string; + + /** + * group + */ + @Property({ nullable: true }) + group?: string; + + /** + * Created at + */ + @Property() + createDate: Date = new Date(); + + /** + * Status + */ + @Property({ nullable: true }) + status?: string; + + /** + * Document instance + */ + @Property({ nullable: true }) + document?: IVC; +} diff --git a/guardian-service/src/entity/vc-document.ts b/guardian-service/src/entity/vc-document.ts index 28ffad22fa..c1cbe8b40e 100644 --- a/guardian-service/src/entity/vc-document.ts +++ b/guardian-service/src/entity/vc-document.ts @@ -20,6 +20,12 @@ export class VcDocument extends BaseEntity implements IVCDocument { @Property({ nullable: true }) assignedTo?: string; + /** + * Assign + */ + @Property({ nullable: true }) + assignedToGroup?: string; + /** * Document hash */ diff --git a/guardian-service/src/hedera-modules/hedera-sdk-helper.ts b/guardian-service/src/hedera-modules/hedera-sdk-helper.ts deleted file mode 100644 index f1dcd2385f..0000000000 --- a/guardian-service/src/hedera-modules/hedera-sdk-helper.ts +++ /dev/null @@ -1,937 +0,0 @@ -import { - AccountBalanceQuery, - AccountCreateTransaction, - AccountId, - AccountInfoQuery, - Client, - Hbar, - HbarUnit, - PrivateKey, - Status, - Timestamp, - TokenAssociateTransaction, - TokenCreateTransaction, - TokenDissociateTransaction, - TokenFreezeTransaction, - TokenGrantKycTransaction, - TokenId, - TokenMintTransaction, - TokenRevokeKycTransaction, - TokenType, - TokenUnfreezeTransaction, - TokenWipeTransaction, - TopicCreateTransaction, - TopicId, - TopicMessageSubmitTransaction, - Transaction, - TransactionReceipt, - TransactionRecord, - TransferTransaction -} from '@hashgraph/sdk'; -import { timeout } from './utils'; -import axios from 'axios'; -import { Environment } from './environment'; -import { TransactionLogger } from './transaction-logger'; -import { GenerateUUIDv4 } from '@guardian/interfaces'; -import Long from 'long'; - -export const MAX_FEE = 10; -export const INITIAL_BALANCE = 30; - -/** - * Contains methods to simplify work with hashgraph sdk - */ -export class HederaSDKHelper { - /** - * Client - * @private - */ - private readonly client: Client; - /** - * Max timeout - */ - public static readonly MAX_TIMEOUT: number = 120000; - /** - * Callback - * @private - */ - private static fn: Function = null; - - /** - * Dry-run - * @private - */ - private readonly dryRun: string = null; - - constructor( - operatorId: string | AccountId | null, - operatorKey: string | PrivateKey | null, - dryRun: string = null - ) { - this.dryRun = dryRun || null; - this.client = Environment.createClient(); - if (operatorId && operatorKey) { - this.client.setOperator(operatorId, operatorKey); - } - } - - /** - * Transaction starting - * @param id - * @param transactionName - * @private - */ - private async transactionStartLog(id: string, transactionName: string): Promise { - await TransactionLogger.transactionLog(id, this.client.operatorAccountId, transactionName); - } - - /** - * Transaction end log - * @param id - * @param transactionName - * @param transaction - * @param metadata - * @private - */ - private async transactionEndLog(id: string, transactionName: string, transaction?: Transaction, metadata?: any): Promise { - await TransactionLogger.transactionLog(id, this.client.operatorAccountId, transactionName, transaction, metadata); - } - - /** - * Transaction error log - * @param id - * @param transactionName - * @param transaction - * @param error - * @private - */ - private async transactionErrorLog(id: string, transactionName: string, transaction: Transaction, error: Error): Promise { - await TransactionLogger.transactionErrorLog(id, this.client.operatorAccountId, transactionName, transaction, error.message); - } - - /** - * Save Virtual Transaction log - * @param id - * @param type - * @param client - * @private - */ - private async virtualTransactionLog(id: string, type: string, client: Client): Promise { - await TransactionLogger.virtualTransactionLog(id, type, client.operatorAccountId?.toString()); - } - - /** - * Create new token (TokenCreateTransaction) - * - * @param {string} name - Token name - * @param {string} symbol - Token symbol - * @param {boolean} nft - Fungible or NonFungible Token - * @param {number} decimals - Decimals - * @param {number} initialSupply - Initial Supply - * @param {string} tokenMemo - Memo field - * @param {any} treasury - treasury account - * @param {PrivateKey} [adminKey] - set admin key - * @param {PrivateKey} [kycKey] - set kyc key - * @param {PrivateKey} [freezeKey] - set freeze key - * @param {PrivateKey} [wipeKey] - set wipe key - * @param {PrivateKey} [supplyKey] - set supply key - * - * @returns {string} - Token id - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async newToken( - name: string, - symbol: string, - nft: boolean, - decimals: number, - initialSupply: number, - tokenMemo: string, - treasury: { - /** - * Id - */ - id: AccountId | string; - /** - * Key - */ - key: PrivateKey; - }, - adminKey: PrivateKey, - kycKey: PrivateKey, - freezeKey: PrivateKey, - wipeKey: PrivateKey, - supplyKey: PrivateKey - ): Promise { - const client = this.client; - - let transaction = new TokenCreateTransaction() - .setTokenName(name) - .setTokenSymbol(symbol) - .setTreasuryAccountId(treasury.id) - .setDecimals(decimals) - .setInitialSupply(initialSupply) - .setTokenMemo(tokenMemo); - - if (adminKey) { - transaction = transaction.setAdminKey(adminKey); - } - if (kycKey) { - transaction = transaction.setKycKey(kycKey); - } - if (freezeKey) { - transaction = transaction.setFreezeKey(freezeKey); - } - if (wipeKey) { - transaction = transaction.setWipeKey(wipeKey); - } - if (supplyKey) { - transaction = transaction.setSupplyKey(supplyKey); - } - if (nft) { - transaction = transaction.setTokenType(TokenType.NonFungibleUnique); - } - transaction = transaction.freezeWith(client); - - const signTx = await (await transaction.sign(adminKey)).sign(treasury.key); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenCreateTransaction'); - const tokenId = receipt.tokenId; - - return tokenId.toString(); - } - - /** - * Get balance account (AccountBalanceQuery) - * - * @param {string | AccountId} accountId - Account Id - * - * @returns {string} - balance - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async balance(accountId: string | AccountId): Promise { - const client = this.client; - const query = new AccountBalanceQuery() - .setAccountId(accountId); - const accountBalance = await query.execute(client); - return accountBalance.hbars.toString(); - } - - /** - * Get associate tokens and balance (AccountInfoQuery) - * - * @param {string | AccountId} accountId - Account Id - * - * @returns {any} - associate tokens and balance - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async accountInfo(accountId?: string | AccountId): Promise { - const client = this.client; - const info = await new AccountInfoQuery() - .setAccountId(accountId) - .execute(client); - const hBarBalance = info.balance.toString(); - const tokens = {}; - for (const key of info.tokenRelationships.keys()) { - const tokenId = key.toString(); - const token = info.tokenRelationships.get(key); - tokens[tokenId] = ({ - tokenId, - balance: token.balance.toString(), - frozen: token.isFrozen, - kyc: token.isKycGranted, - hBarBalance - }); - } - return tokens; - } - - /** - * Associate tokens with account (TokenAssociateTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string} id - Account Id - * @param {string} key - Account Private Id - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async associate(tokenId: string | TokenId, id: string, key: string): Promise { - const client = this.client; - - const accountId = AccountId.fromString(id); - const accountKey = PrivateKey.fromString(key); - const transaction = new TokenAssociateTransaction() - .setAccountId(accountId) - .setTokenIds([tokenId]) - .freezeWith(client); - const signTx = await transaction.sign(accountKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenAssociateTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Dissociate tokens with account (TokenDissociateTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string} id - Account Id - * @param {string} key - Account Private Id - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async dissociate(tokenId: string | TokenId, id: string, key: string): Promise { - const client = this.client; - - const accountId = AccountId.fromString(id); - const accountKey = PrivateKey.fromString(key); - const transaction = new TokenDissociateTransaction() - .setAccountId(accountId) - .setTokenIds([tokenId]) - .freezeWith(client); - const signTx = await transaction.sign(accountKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenDissociateTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Freezes transfers of the specified token for the account (TokenFreezeTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string} accountId - Account Id - * @param {string} freezeKey - Token freeze key - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async freeze(tokenId: string | TokenId, accountId: string, freezeKey: string): Promise { - const client = this.client; - - const _freezeKey = PrivateKey.fromString(freezeKey); - const transaction = new TokenFreezeTransaction() - .setAccountId(accountId) - .setTokenId(tokenId) - .freezeWith(client); - const signTx = await transaction.sign(_freezeKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenFreezeTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Unfreezes transfers of the specified token for the account (TokenUnfreezeTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string} accountId - Account Id - * @param {string} freezeKey - Token freeze key - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async unfreeze(tokenId: string | TokenId, accountId: string, freezeKey: string): Promise { - const client = this.client; - - const _freezeKey = PrivateKey.fromString(freezeKey); - const transaction = new TokenUnfreezeTransaction() - .setAccountId(accountId) - .setTokenId(tokenId) - .freezeWith(client); - const signTx = await transaction.sign(_freezeKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenUnfreezeTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Grants KYC to the account for the given token (TokenGrantKycTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string} accountId - Account Id - * @param {string} kycKey - Token KYC key - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async grantKyc(tokenId: string | TokenId, accountId: string, kycKey: string): Promise { - const client = this.client; - - const _kycKey = PrivateKey.fromString(kycKey); - const transaction = new TokenGrantKycTransaction() - .setAccountId(accountId) - .setTokenId(tokenId) - .freezeWith(client); - const signTx = await transaction.sign(_kycKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenGrantKycTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Revokes the KYC to the account for the given token (TokenRevokeKycTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string} accountId - Account Id - * @param {string} kycKey - Token KYC key - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async revokeKyc(tokenId: string | TokenId, accountId: string, kycKey: string): Promise { - const client = this.client; - - const _kycKey = PrivateKey.fromString(kycKey); - const transaction = new TokenRevokeKycTransaction() - .setAccountId(accountId) - .setTokenId(tokenId) - .freezeWith(client); - const signTx = await transaction.sign(_kycKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenRevokeKycTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Minting fungible token allows you to increase the total supply of the token (TokenMintTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string | PrivateKey} supplyKey - Token Supply key - * @param {number} amount - amount - * @param {string} [transactionMemo] - Memo field - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async mint( - tokenId: string | TokenId, - supplyKey: string | PrivateKey, - amount: number, - transactionMemo?: string - ): Promise { - const client = this.client; - - const _supplyKey = PrivateKey.fromString(supplyKey.toString()); - const transaction = new TokenMintTransaction() - .setTokenId(tokenId) - .setAmount(amount) - .setTransactionMemo(transactionMemo) - .freezeWith(client); - const signTx = await transaction.sign(_supplyKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenMintTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Minting a non-fungible token creates an NFT with - * its unique metadata for the class of NFTs defined by the token ID (TokenMintTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string | PrivateKey} supplyKey - Token Supply key - * @param {Uint8Array[]} data - token data - * @param {string} [transactionMemo] - Memo field - * - * @returns {number[]} - serials - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async mintNFT( - tokenId: string | TokenId, - supplyKey: string | PrivateKey, - data: Uint8Array[], - transactionMemo?: string - ): Promise { - const client = this.client; - - const _supplyKey = PrivateKey.fromString(supplyKey.toString()); - const transaction = new TokenMintTransaction() - .setTokenId(tokenId) - .setMetadata(data) - .setTransactionMemo(transactionMemo) - .freezeWith(client); - const signTx = await transaction.sign(_supplyKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenMintNFTTransaction'); - const transactionStatus = receipt.status; - - if (transactionStatus === Status.Success) { - return receipt.serials.map(e => e.toNumber()) - } else { - return null; - } - } - - /** - * Wipes the provided amount of fungible tokens from the specified account (TokenWipeTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string | AccountId} targetId - Target Account Id - * @param {string | PrivateKey} wipeKey - Token Wipe key - * @param {number} amount - amount - * @param {string} [transactionMemo] - Memo field - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async wipe( - tokenId: string | TokenId, - targetId: string | AccountId, - wipeKey: string | PrivateKey, - amount: number, - transactionMemo?: string - ): Promise { - const client = this.client; - - const _wipeKey = PrivateKey.fromString(wipeKey.toString()); - const transaction = new TokenWipeTransaction() - .setAccountId(targetId) - .setTokenId(tokenId) - .setAmount(amount) - .setTransactionMemo(transactionMemo) - .freezeWith(client); - const signTx = await transaction.sign(_wipeKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TokenWipeTransaction'); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Transfer tokens from some accounts to other accounts (TransferTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string | AccountId} targetId - Target Account Id - * @param {string | AccountId} scoreId - Treasury Account Id - * @param {string | PrivateKey} scoreKey - Token Score key - * @param {number} amount - amount - * @param {string} [transactionMemo] - Memo field - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async transfer( - tokenId: string | TokenId, - targetId: string | AccountId, - scoreId: string | AccountId, - scoreKey: string | PrivateKey, - amount: number, - transactionMemo?: string - ): Promise { - const client = this.client; - - const _scoreKey = PrivateKey.fromString(scoreKey.toString()); - const transaction = new TransferTransaction() - .addTokenTransfer(tokenId, scoreId, -amount) - .addTokenTransfer(tokenId, targetId, amount) - .setTransactionMemo(transactionMemo) - .freezeWith(client); - const signTx = await transaction.sign(_scoreKey); - const receipt = await this.executeAndReceipt(client, signTx, 'TransferTransaction', amount); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Transfer non-fungible token from some accounts to other accounts (TransferTransaction) - * - * @param {string | TokenId} tokenId - Token Id - * @param {string | AccountId} targetId - Target Account Id - * @param {string | AccountId} scoreId - Treasury Account Id - * @param {string | PrivateKey} scoreKey - Token Score key - * @param {number[]} serials - serials - * @param {string} [transactionMemo] - Memo field - * - * @returns {boolean} - Status - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async transferNFT( - tokenId: string | TokenId, - targetId: string | AccountId, - scoreId: string | AccountId, - scoreKey: string | PrivateKey, - serials: number[], - transactionMemo?: string - ): Promise { - const client = this.client; - - const _scoreKey = PrivateKey.fromString(scoreKey.toString()); - let transaction = new TransferTransaction() - .setTransactionMemo(transactionMemo); - - for (const serial of serials) { - transaction = transaction - .addNftTransfer(tokenId, serial, scoreId, targetId) - - } - transaction = transaction.freezeWith(client); - const signTx = await transaction.sign(_scoreKey); - const receipt = await this.executeAndReceipt(client, signTx, 'NFTTransferTransaction', serials); - const transactionStatus = receipt.status; - - return transactionStatus === Status.Success; - } - - /** - * Create new Account (AccountCreateTransaction) - * - * @param {number} initialBalance - Initial Balance - * - * @returns {any} - Account Id and Account Private Key - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async newAccount(initialBalance: number): Promise<{ - /** - * Account ID - */ - id: AccountId; - /** - * Private key - */ - key: PrivateKey; - }> { - const client = this.client; - - const newPrivateKey = PrivateKey.generate(); - const transaction = new AccountCreateTransaction() - .setKey(newPrivateKey.publicKey) - .setInitialBalance(new Hbar(initialBalance || INITIAL_BALANCE)); - const receipt = await this.executeAndReceipt(client, transaction, 'AccountCreateTransaction'); - const newAccountId = receipt.accountId; - - return { - id: newAccountId, - key: newPrivateKey - }; - } - - /** - * New treasury - * @param accountId - * @param privateKey - */ - public newTreasury(accountId: string | AccountId, privateKey: string | PrivateKey) { - return { - id: typeof accountId === 'string' ? AccountId.fromString(accountId) : accountId, - key: typeof privateKey === 'string' ? PrivateKey.fromString(privateKey) : privateKey - }; - } - - /** - * Create new Topic (TopicCreateTransaction) - * - * @param {PrivateKey | string} [key] - Topic Admin Key - * - * @returns {string} - Topic Id - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async newTopic( - adminKey?: PrivateKey | string, - submitKey?: PrivateKey | string, - topicMemo?: string - ): Promise { - const client = this.client; - - let transaction: any = new TopicCreateTransaction() - - if (topicMemo) { - transaction = transaction.setTopicMemo(topicMemo.substring(0, 100)); - } - - if (submitKey) { - const accountKey = PrivateKey.fromString(submitKey.toString()); - transaction = transaction.setSubmitKey(accountKey); - } - - if (adminKey) { - const accountKey = PrivateKey.fromString(adminKey.toString()); - transaction = transaction.setAdminKey(accountKey) - } - - transaction = transaction.freezeWith(client); - - if (adminKey) { - const accountKey = PrivateKey.fromString(adminKey.toString()); - transaction = await transaction.sign(accountKey); - } - - const receipt = await this.executeAndReceipt(client, transaction, 'TopicCreateTransaction'); - const topicId = receipt.topicId; - - return topicId.toString(); - } - - /** - * Submit message to the topic (TopicMessageSubmitTransaction) - * - * @param topicId Topic identifier - * @param message Message to publish - * - * @returns Message timestamp - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async submitMessage( - topicId: string | TopicId, - message: string, - privateKey?: string | PrivateKey, - transactionMemo?: string - ): Promise { - const client = this.client; - - let messageTransaction: Transaction = new TopicMessageSubmitTransaction({ - topicId, - message, - }); - - if (transactionMemo) { - messageTransaction = messageTransaction.setTransactionMemo(transactionMemo.substring(0, 100)); - } - - if (privateKey) { - messageTransaction = messageTransaction.freezeWith(client); - if (typeof privateKey === 'string') { - messageTransaction = await messageTransaction.sign(PrivateKey.fromString(privateKey)); - } else { - messageTransaction = await messageTransaction.sign(privateKey); - } - } - const rec = await this.executeAndRecord(client, messageTransaction, 'TopicMessageSubmitTransaction'); - const seconds = rec.consensusTimestamp.seconds.toString(); - const nanos = rec.consensusTimestamp.nanos.toString(); - - return (seconds + '.' + ('000000000' + nanos).slice(-9)); - } - - /** - * Returns topic message - * @param timeStamp Message identifier - * @returns Message - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async getTopicMessage(timeStamp: string): Promise<{ - /** - * Topic ID - */ - topicId: string, - /** - * Message - */ - message: string - }> { - const res = await axios.get( - `${Environment.HEDERA_MESSAGE_API}/${timeStamp}`, - { responseType: 'json' } - ); - if (!res || !res.data || !res.data.message) { - throw new Error(`Invalid message '${timeStamp}'`); - } - const buffer = Buffer.from(res.data.message, 'base64').toString(); - const topicId = res.data.topic_id; - return { - topicId, - message: buffer - } - } - - /** - * Returns topic messages - * @param topicId Topic identifier - * @returns Messages - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public async getTopicMessages(topicId: string): Promise<{ - /** - * ID - */ - id: string, - /** - * Message - */ - message: string - }[]> { - const res = await axios.get(`${Environment.HEDERA_TOPIC_API}${topicId}/messages`, { - params: { limit: Number.MAX_SAFE_INTEGER }, - responseType: 'json' - }); - - if (!res || !res.data || !res.data.messages) { - throw new Error(`Invalid topicId '${topicId}'`); - } - - const result = []; - const messages = res.data.messages; - if (messages.length === 0) { - return result; - } - - for (const m of messages) { - const buffer = Buffer.from(m.message, 'base64').toString(); - const id = m.consensus_timestamp; - result.push({ - id, - message: buffer - }); - } - - return result; - } - - /** - * Execute and receipt - * @param client - * @param transaction - * @param type - * @param metadata - * @private - */ - private async executeAndReceipt( - client: Client, transaction: Transaction, type: string, metadata?: any - ): Promise { - if (this.dryRun) { - await this.virtualTransactionLog(this.dryRun, type, client); - return { - status: Status.Success, - topicId: new TokenId(Date.now()), - tokenId: new TokenId(Date.now()), - accountId: new AccountId(Date.now()), - serials: [Long.fromInt(1)] - } as any - } else { - const id = GenerateUUIDv4(); - try { - await this.transactionStartLog(id, type); - const result = await transaction.execute(client); - const receipt = await result.getReceipt(client); - await this.transactionEndLog(id, type, transaction, metadata); - HederaSDKHelper.transactionResponse(client); - return receipt; - } catch (error) { - await this.transactionErrorLog(id, type, transaction, error); - throw error; - } - } - } - - /** - * Execute and record - * @param client - * @param transaction - * @param type - * @param metadata - * @private - */ - private async executeAndRecord( - client: Client, transaction: Transaction, type: string, metadata?: any - ): Promise { - if (this.dryRun) { - await this.virtualTransactionLog(this.dryRun, type, client); - return { - consensusTimestamp: Timestamp.fromDate(Date.now()) - } as any - } else { - const id = GenerateUUIDv4(); - try { - await this.transactionStartLog(id, type); - const result = await transaction.execute(client); - const record = await result.getRecord(client); - await this.transactionEndLog(id, type, transaction, metadata); - HederaSDKHelper.transactionResponse(client); - return record; - } catch (error) { - await this.transactionErrorLog(id, type, transaction, error); - throw error; - } - } - } - - /** - * Set transaction response callback - * @param fn - */ - public static setTransactionResponseCallback(fn: Function) { - HederaSDKHelper.fn = fn; - } - - /** - * Transaction response - * @param client - * @private - */ - private static transactionResponse(client: Client) { - if (HederaSDKHelper.fn) { - HederaSDKHelper.fn(client); - } - } - - /** - * Crate client - * @param operatorId - * @param operatorKey - */ - public static client(operatorId?: string | AccountId, operatorKey?: string | PrivateKey) { - const client = Environment.createClient(); - if (operatorId && operatorKey) { - client.setOperator(operatorId, operatorKey); - } - return client; - } - - /** - * Get balance account (AccountBalanceQuery) - * - * @param {string | AccountId} accountId - Account Id - * - * @returns {string} - balance - */ - @timeout(HederaSDKHelper.MAX_TIMEOUT) - public static async balance(client: Client, accountId: string | AccountId): Promise { - const query = new AccountBalanceQuery() - .setAccountId(accountId); - const accountBalance = await query.execute(client); - if (accountBalance && accountBalance.hbars) { - return accountBalance.hbars.to(HbarUnit.Hbar).toNumber(); - } - return NaN; - } - - /** - * Check Account - * @param accountId - */ - public static checkAccount(accountId: string): boolean { - if (accountId) { - try { - AccountId.fromString(accountId); - return true; - } catch (error) { - return false; - } - } - return false; - } - - /** - * Create Virtual Account - */ - public static async createVirtualAccount(): Promise<{ - /** - * Account ID - */ - id: AccountId; - /** - * Private key - */ - key: PrivateKey; - }> { - const newPrivateKey = PrivateKey.generate(); - const newAccountId = new AccountId(Date.now()); - return { - id: newAccountId, - key: newPrivateKey - }; - } -} diff --git a/guardian-service/src/hedera-modules/index.ts b/guardian-service/src/hedera-modules/index.ts index ec66c8c74c..40443d1060 100644 --- a/guardian-service/src/hedera-modules/index.ts +++ b/guardian-service/src/hedera-modules/index.ts @@ -1,7 +1,5 @@ -export { HederaSDKHelper } from './hedera-sdk-helper'; export { timeout } from './utils'; export { Message, MessageStatus } from './message/message'; -export { HederaUtils } from './utils'; export { VcDocument } from './vcjs/vc-document'; export { VcSubject } from './vcjs/vc-subject'; export { VpDocument } from './vcjs/vp-document'; @@ -27,4 +25,5 @@ export { TransactionLogger, TransactionLogLvl } from './transaction-logger'; export { RegistrationMessage } from './message/registration-message'; export { TopicHelper } from './topic-helper'; export { MessageMemo } from './memo-mappings/message-memo'; -export { TopicMemo } from './memo-mappings/topic-memo'; \ No newline at end of file +export { TopicMemo } from './memo-mappings/topic-memo'; +export { TokenMessage } from './message/token-message'; diff --git a/guardian-service/src/hedera-modules/memo-mappings/message-memo.ts b/guardian-service/src/hedera-modules/memo-mappings/message-memo.ts index b5752f2b8a..a00a151354 100644 --- a/guardian-service/src/hedera-modules/memo-mappings/message-memo.ts +++ b/guardian-service/src/hedera-modules/memo-mappings/message-memo.ts @@ -85,6 +85,7 @@ export class MessageMemo extends MemoMap { messageMemo[`${MessageType.Topic}.${MessageAction.CreateTopic}.${TopicType.InstancePolicyTopic}`] = 'Policy Instance topic creation message'; messageMemo[`${MessageType.Topic}.${MessageAction.CreateTopic}.${TopicType.UserTopic}`] = 'Standard Registry topic creation message'; messageMemo[`${MessageType.Topic}.${MessageAction.CreateTopic}.${TopicType.DynamicTopic}`] = '${name} operation topic creation message'; + messageMemo[`${MessageType.Token}.${MessageAction.UseToken}`] = 'Policy token issue message'; messageMemo[MessageAction.ChangeMessageStatus] = 'Status change message'; messageMemo[MessageAction.RevokeDocument] = 'Revoke document message'; messageMemo[MessageAction.DeleteDocument] = 'Delete document message'; diff --git a/guardian-service/src/hedera-modules/message/message-action.ts b/guardian-service/src/hedera-modules/message/message-action.ts index 8539fa095a..35282edb45 100644 --- a/guardian-service/src/hedera-modules/message/message-action.ts +++ b/guardian-service/src/hedera-modules/message/message-action.ts @@ -16,5 +16,6 @@ export enum MessageAction { Init = 'Initialization', ChangeMessageStatus = 'change-message-status', RevokeDocument = 'revoke-document', - DeleteDocument = 'delete-document' + DeleteDocument = 'delete-document', + UseToken = 'token-issue' } diff --git a/guardian-service/src/hedera-modules/message/message-body.interface.ts b/guardian-service/src/hedera-modules/message/message-body.interface.ts index 51107ad4ff..87d3da48a9 100644 --- a/guardian-service/src/hedera-modules/message/message-body.interface.ts +++ b/guardian-service/src/hedera-modules/message/message-body.interface.ts @@ -265,3 +265,38 @@ export interface RegistrationMessageBody extends MessageBody { */ attributes: { [x: string]: string } | undefined; } + +/** + * Token message body + */ +export interface TokenMessageBody extends MessageBody { + /** + * Token id + */ + tokenId: string; + + /** + * Token name + */ + tokenName: string; + + /** + * Token symbol + */ + tokenSymbol: string; + + /** + * Token type + */ + tokenType: string; + + /** + * Token decimals + */ + decimals: string; + + /** + * Owner + */ + owner: string; +} \ No newline at end of file diff --git a/guardian-service/src/hedera-modules/message/message-server.ts b/guardian-service/src/hedera-modules/message/message-server.ts index 21a2994db8..8d98630e9c 100644 --- a/guardian-service/src/hedera-modules/message/message-server.ts +++ b/guardian-service/src/hedera-modules/message/message-server.ts @@ -5,7 +5,6 @@ import { } from '@hashgraph/sdk'; import { IPFS } from '@helpers/ipfs'; import { Message } from './message'; -import { HederaSDKHelper } from '../hedera-sdk-helper'; import { MessageType } from './message-type'; import { VCMessage } from './vc-message'; import { DIDMessage } from './did-message'; @@ -25,13 +24,6 @@ import { MessageMemo } from '../memo-mappings/message-memo'; * Message server */ export class MessageServer { - - /** - * Client - * @private - */ - private readonly client: HederaSDKHelper; - /** * Submit key * @private @@ -69,7 +61,6 @@ export class MessageServer { this.clientOptions = {operatorId, operatorKey, dryRun}; this.dryRun = dryRun || null; - this.client = new HederaSDKHelper(operatorId, operatorKey, dryRun); } /** @@ -88,7 +79,7 @@ export class MessageServer { await TransactionLogger.virtualFileLog(this.dryRun, file, result); return result } - return IPFS.addFileAsync(file); + return IPFS.addFile(file); } /** @@ -102,7 +93,7 @@ export class MessageServer { if (this.dryRun) { throw new Error('Unable to get virtual file'); } - return IPFS.getFileAsync(cid, responseType); + return await IPFS.getFile(cid, responseType); } /** @@ -295,7 +286,19 @@ export class MessageServer { * @private */ private async getTopicMessage(timeStamp: string, type?: MessageType): Promise { - const { topicId, message } = await this.client.getTopicMessage(timeStamp); + const {operatorId, operatorKey, dryRun} = this.clientOptions; + + const workers = new Workers(); + const { topicId, message } = await workers.addTask({ + type: WorkerTaskType.GET_TOPIC_MESSAGE, + data: { + operatorId, + operatorKey, + dryRun, + timeStamp + } + }, 1); + new Logger().info(`getTopicMessage, ${timeStamp}, ${topicId}, ${message}`, ['GUARDIAN_SERVICE']); const result = MessageServer.fromMessage(message, type); result.setId(timeStamp); @@ -311,8 +314,20 @@ export class MessageServer { * @private */ private async getTopicMessages(topicId: string | TopicId, type?: MessageType, action?: MessageAction): Promise { + const {operatorId, operatorKey, dryRun} = this.clientOptions; + const topic = topicId.toString(); - const messages = await this.client.getTopicMessages(topic); + const workers = new Workers(); + const messages = await workers.addTask({ + type: WorkerTaskType.GET_TOPIC_MESSAGES, + data: { + operatorId, + operatorKey, + dryRun, + topic + } + }, 1); + new Logger().info(`getTopicMessages, ${topic}`, ['GUARDIAN_SERVICE']); const result: Message[] = []; for (const message of messages) { @@ -423,7 +438,18 @@ export class MessageServer { public async findTopic(messageId: string): Promise { try { if (messageId) { - const { topicId } = await this.client.getTopicMessage(messageId); + const {operatorId, operatorKey, dryRun} = this.clientOptions; + + const workers = new Workers(); + const { topicId } = await workers.addTask({ + type: WorkerTaskType.GET_TOPIC_MESSAGE, + data: { + operatorId, + operatorKey, + dryRun, + timeStamp: messageId + } + }, 1); return topicId; } return null; diff --git a/guardian-service/src/hedera-modules/message/message-type.ts b/guardian-service/src/hedera-modules/message/message-type.ts index b346f83687..2e6d7cd129 100644 --- a/guardian-service/src/hedera-modules/message/message-type.ts +++ b/guardian-service/src/hedera-modules/message/message-type.ts @@ -10,5 +10,5 @@ export enum MessageType { Schema = 'Schema', Topic = 'Topic', StandardRegistry = 'Standard Registry', - // DynamicTopic = 'Dynamic-Topic', + Token = 'Token' } diff --git a/guardian-service/src/hedera-modules/message/token-message.ts b/guardian-service/src/hedera-modules/message/token-message.ts new file mode 100644 index 0000000000..e1f786b40c --- /dev/null +++ b/guardian-service/src/hedera-modules/message/token-message.ts @@ -0,0 +1,178 @@ +import { Message } from './message'; +import { IURL } from './url.interface'; +import { MessageAction } from './message-action'; +import { MessageType } from './message-type'; +import { TokenMessageBody } from './message-body.interface'; + +/** + * Token message + */ +export class TokenMessage extends Message { + /** + * Token id + */ + public tokenId: string; + + /** + * Token name + */ + public tokenName: string; + + /** + * Token symbol + */ + public tokenSymbol: string; + + /** + * Token type + */ + public tokenType: string; + + /** + * Token decimals + */ + public decimals: string; + + /** + * Owner + */ + public owner: string; + + constructor(action: MessageAction) { + super(action, MessageType.Token); + } + + /** + * Set document + * @param token + */ + public setDocument(token: { + /** + * Token id + */ + tokenId?: string, + + /** + * Token name + */ + tokenName?: string, + + /** + * Token symbol + */ + tokenSymbol?: string, + + /** + * Token type + */ + tokenType?: string, + + /** + * Token decimals + */ + decimals?: string, + + /** + * Owner + */ + owner?: string, + }): void { + this.tokenId = token.tokenId; + this.tokenName = token.tokenName; + this.tokenSymbol = token.tokenSymbol; + this.tokenType = token.tokenType; + this.decimals = token.decimals; + this.owner = token.owner; + } + + /** + * To message object + */ + public override toMessageObject(): TokenMessageBody { + return { + id: null, + status: null, + type: this.type, + action: this.action, + lang: this.lang, + tokenId: this.tokenId, + tokenName: this.tokenName, + tokenSymbol: this.tokenSymbol, + tokenType: this.tokenType, + decimals: this.decimals, + owner: this.owner + }; + } + + /** + * To documents + */ + public async toDocuments(): Promise { + return []; + } + + /** + * Load documents + * @param documents + */ + public loadDocuments(documents: string[]): TokenMessage { + return this; + } + + /** + * From message + * @param message + */ + public static fromMessage(message: string): TokenMessage { + if (!message) { + throw new Error('Message Object is empty'); + } + + const json = JSON.parse(message); + return TokenMessage.fromMessageObject(json); + } + + /** + * From message object + * @param json + */ + public static fromMessageObject(json: TokenMessageBody): TokenMessage { + if (!json) { + throw new Error('JSON Object is empty'); + } + + if (json.type !== MessageType.Token) { + throw new Error('Invalid message type'); + } + + let message = new TokenMessage(json.action); + message = Message._fromMessageObject(message, json); + message._id = json.id; + message._status = json.status; + + message.tokenId = json.tokenId; + message.tokenName = json.tokenName; + message.tokenSymbol = json.tokenSymbol; + message.tokenType = json.tokenType; + message.decimals = json.decimals; + message.owner = json.owner; + + const urls = [] + message.setUrls(urls); + return message; + } + + /** + * Validate + */ + public override validate(): boolean { + return true; + } + + /** + * Get URLs + */ + public getUrls(): IURL[] { + return []; + } +} diff --git a/guardian-service/src/hedera-modules/topic-helper.ts b/guardian-service/src/hedera-modules/topic-helper.ts index 923c3f702d..422c0e605a 100644 --- a/guardian-service/src/hedera-modules/topic-helper.ts +++ b/guardian-service/src/hedera-modules/topic-helper.ts @@ -1,7 +1,8 @@ -import { TopicType } from '@guardian/interfaces'; -import { HederaSDKHelper, MessageAction, MessageServer, TopicMessage } from '@hedera-modules'; +import { TopicType, WorkerTaskType } from '@guardian/interfaces'; +import { MessageAction, MessageServer, TopicMessage } from '@hedera-modules'; import { Topic } from '@entity/topic'; import { TopicMemo } from './memo-mappings/topic-memo'; +import { Workers } from '@helpers/workers'; /** * Topic Helper @@ -86,12 +87,18 @@ export class TopicHelper { memoObj?: any } ): Promise { - const client = new HederaSDKHelper(this.hederaAccountId, this.hederaAccountKey, this.dryRun); - const topicId = await client.newTopic( - this.hederaAccountKey, - this.hederaAccountKey, - TopicMemo.parseMemo(true, config.memo, config.memoObj) || TopicMemo.getTopicMemo(config) - ); + + const workers = new Workers(); + const topicId = await workers.addTask({ + type: WorkerTaskType.NEW_TOPIC, + data: { + hederaAccountId: this.hederaAccountId, + hederaAccountKey: this.hederaAccountKey, + dryRun: this.dryRun, + topicMemo: TopicMemo.parseMemo(true, config.memo, config.memoObj) || TopicMemo.getTopicMemo(config) + } + }, 1); + return { topicId, name: config.name, @@ -168,4 +175,4 @@ export class TopicHelper { .sendMessage(message2); } } -} \ No newline at end of file +} diff --git a/guardian-service/src/hedera-modules/transaction-logger.ts b/guardian-service/src/hedera-modules/transaction-logger.ts index 484f80cee6..66751aeb1b 100644 --- a/guardian-service/src/hedera-modules/transaction-logger.ts +++ b/guardian-service/src/hedera-modules/transaction-logger.ts @@ -15,7 +15,9 @@ import { Transaction, TransferTransaction } from '@hashgraph/sdk'; -import { HederaSDKHelper } from './hedera-sdk-helper'; +import { WorkerTaskType } from '@guardian/interfaces'; +import { Workers } from '@helpers/workers'; +import { SettingsContainer } from '@guardian/common'; /** * Transaction log level @@ -172,8 +174,16 @@ export class TransactionLogger { if (TransactionLogger.logLvl === TransactionLogLvl.DEBUG) { try { - const client = new HederaSDKHelper(process.env.OPERATOR_ID, process.env.OPERATOR_KEY); - const balance = await client.balance(operatorAccountId); + const settingsContainer = new SettingsContainer(); + const {OPERATOR_ID, OPERATOR_KEY} = settingsContainer.settings; + const workers = new Workers(); + const balance = await workers.addTask({ + type: WorkerTaskType.GET_USER_BALANCE, + data: { + hederaAccountId: OPERATOR_ID, + hederaAccountKey: OPERATOR_KEY + } + }, 1); attr.push(balance); } catch (error) { attr.push(null); diff --git a/guardian-service/src/hedera-modules/utils.ts b/guardian-service/src/hedera-modules/utils.ts index d70859b4be..d467f2515e 100644 --- a/guardian-service/src/hedera-modules/utils.ts +++ b/guardian-service/src/hedera-modules/utils.ts @@ -1,5 +1,3 @@ -import { PrivateKey } from '@hashgraph/sdk'; - /** * Timeout decorator * @param timeoutValue @@ -17,32 +15,3 @@ export function timeout(timeoutValue: number) { } } } - -/** - * Hedera utils class - */ -export class HederaUtils { - /** - * Generate random key - */ - public static randomKey(): string { - const privateKey = PrivateKey.generate(); - return HederaUtils.encode(privateKey.toBytes()); - } - - /** - * Encode - * @param data - */ - public static encode(data: Uint8Array): string { - return Buffer.from(data).toString(); - } - - /** - * Decode - * @param text - */ - public static decode(text: string): Uint8Array { - return new Uint8Array(Buffer.from(text)); - } -} diff --git a/guardian-service/src/hedera-modules/vcjs/vcjs.ts b/guardian-service/src/hedera-modules/vcjs/vcjs.ts index 18fada2b9e..93d479699e 100644 --- a/guardian-service/src/hedera-modules/vcjs/vcjs.ts +++ b/guardian-service/src/hedera-modules/vcjs/vcjs.ts @@ -14,6 +14,7 @@ import { DocumentLoader } from '../document-loader/document-loader'; import { SchemaLoader, SchemaLoaderFunction } from '../document-loader/schema-loader'; import { DidRootKey } from './did-document'; import { Issuer } from './issuer'; +import axios from 'axios'; /** * Suite interface @@ -235,12 +236,14 @@ export class VCJS { throw new Error('Schema not found'); } - const ajv = new Ajv(); + const ajv = new Ajv({ + loadSchema: this.loadSchema + }); addFormats(ajv); this.prepareSchema(schema); - const validate = ajv.compile(schema); + const validate = await ajv.compileAsync(schema); const valid = validate(vc); return new CheckResult(valid, 'JSON_SCHEMA_VALIDATION_ERROR', validate.errors as any); @@ -251,7 +254,7 @@ export class VCJS { * * @param {HcsVcDocument} vcDocument - VC Document * - * @returns {boolean} - is verified + * @returns {Promise} - is verified */ public async verifyVC(vcDocument: VcDocument | any): Promise { let vc: IVC; @@ -369,12 +372,14 @@ export class VCJS { throw new Error('Schema not found'); } - const ajv = new Ajv(); + const ajv = new Ajv({ + loadSchema: this.loadSchema + }); addFormats(ajv); this.prepareSchema(schema); - const validate = ajv.compile(schema); + const validate = await ajv.compileAsync(schema); const valid = validate(subject); @@ -413,4 +418,18 @@ export class VCJS { } return subject; } + + /** + * Load schema by URI + * @param uri URI + * @returns Schema + */ + public async loadSchema(uri) { + try { + const response = await axios.get(uri); + return response.data; + } catch (err) { + throw new Error('Can not resolve reference: ' + uri); + } + } } \ No newline at end of file diff --git a/guardian-service/src/helpers/ipfs.ts b/guardian-service/src/helpers/ipfs.ts index 280ab5ce49..96f02bc4c9 100644 --- a/guardian-service/src/helpers/ipfs.ts +++ b/guardian-service/src/helpers/ipfs.ts @@ -1,6 +1,5 @@ import { MessageBrokerChannel } from '@guardian/common'; -import { MessageAPI, IFileResponse, WorkerTaskType } from '@guardian/interfaces'; -import { IPFSTaskManager } from './ipfs-task-manager'; +import { MessageAPI, WorkerTaskType } from '@guardian/interfaces'; import { Workers } from '@helpers/workers'; /** @@ -48,45 +47,11 @@ export class IPFS { * URL */ url: string - }> { - const res = await new Workers().addTask({ - type: WorkerTaskType.ADD_FILE, - data: { - target: [IPFS.target, MessageAPI.IPFS_ADD_FILE].join('.'), - payload: { - content: Buffer.from(file).toString('base64') - } - } - }, 10); - - if (!res) { - throw new Error('Invalid response'); - } - if (res.error) { - throw new Error(res.error); - } - return res.body; - } - - /** - * Async add file and prosime of adding - * @param {ArrayBuffer} file file to upload on IPFS - * @returns {string} - hash - */ - public static async addFileAsync(file: ArrayBuffer): Promise<{ - /** - * CID - */ - cid: string, - /** - * URL - */ - url: string }> { const res = await new Workers().addTask({ type: WorkerTaskType.ADD_FILE, data: { - target: [IPFS.target, MessageAPI.IPFS_ADD_FILE_ASYNC].join('.'), + target: [IPFS.target, MessageAPI.IPFS_ADD_FILE].join('.'), payload: { content: Buffer.from(file).toString('base64') } @@ -95,19 +60,7 @@ export class IPFS { if (!res) { throw new Error('Invalid response'); } - if (res.error) { - throw new Error(res.error); - } - - const { taskId } = res.body; - if (!taskId) { - throw new Error('Invalid response: taskId excepted'); - } - const addFilePromise = new Promise((resolve, reject) => { - IPFSTaskManager.AddTask(taskId, resolve, reject); - }); - - return addFilePromise; + return res; } /** @@ -127,40 +80,6 @@ export class IPFS { if (!res) { throw new Error('Invalid response'); } - if (res.error) { - throw new Error(res.error); - } - return res.body; - } - - /** - * Async returns file by IPFS CID - * @param cid IPFS CID - * @param responseType Response type - * @returns File - */ - public static async getFileAsync(cid: string, responseType: 'json' | 'raw' | 'str'): Promise { - const res = await new Workers().addTask({ - type: WorkerTaskType.GET_FILE, - data: { - target: [IPFS.target, MessageAPI.IPFS_GET_FILE_ASYNC].join('.'), - payload: { cid, responseType } - } - }, 10); - if (!res) { - throw new Error('Invalid response'); - } - if (res.error) { - throw new Error(res.error); - } - - const { taskId } = res.body; - if (!taskId) { - throw new Error('Invalid response: taskId excepted'); - } - const getFilePromise = new Promise((resolve, reject) => { - IPFSTaskManager.AddTask(taskId, resolve, reject); - }); - return getFilePromise; + return res; } } diff --git a/guardian-service/src/helpers/notifier.ts b/guardian-service/src/helpers/notifier.ts index c8343c1ab6..5749f279f2 100644 --- a/guardian-service/src/helpers/notifier.ts +++ b/guardian-service/src/helpers/notifier.ts @@ -83,7 +83,7 @@ export function initNotifier(channel: MessageBrokerChannel, taskId: string): INo currentStep = nextStep; await sendStatuses({ message: oldStep, type: StatusType.COMPLETED }, { message: currentStep, type: StatusType.PROCESSING }); } else { - this.start(nextStep); + notifier.start(nextStep); } }, info: async (message: string) => { diff --git a/guardian-service/src/helpers/users.ts b/guardian-service/src/helpers/users.ts index 85e77688f8..a72f3fab12 100644 --- a/guardian-service/src/helpers/users.ts +++ b/guardian-service/src/helpers/users.ts @@ -1,5 +1,5 @@ import { Singleton } from '@helpers/decorators/singleton'; -import { AuthEvents, UserRole } from '@guardian/interfaces'; +import { AuthEvents, IRootConfig, UserRole } from '@guardian/interfaces'; import { ServiceRequestsBase } from '@helpers/service-requests-base'; import { KeyType, Wallet } from '@helpers/wallet'; import { Inject } from '@helpers/decorators/inject'; @@ -177,20 +177,7 @@ export class Users extends ServiceRequestsBase { * Get hedera account * @param did */ - public async getHederaAccount(did: string): Promise<{ - /** - * Account id - */ - hederaAccountId: string; - /** - * Account key - */ - hederaAccountKey: string; - /** - * DID - */ - did: string; - }> { + public async getHederaAccount(did: string): Promise { if (!did) { throw new Error('Invalid DID'); } diff --git a/guardian-service/src/helpers/utils.ts b/guardian-service/src/helpers/utils.ts index df89956af0..edb68c24e7 100644 --- a/guardian-service/src/helpers/utils.ts +++ b/guardian-service/src/helpers/utils.ts @@ -1,4 +1,10 @@ import { IVC, IVCDocument, GenerateUUIDv4 } from '@guardian/interfaces'; +import { Client } from '@hashgraph/sdk'; + +/** + * Transaction response callback + */ +let TransactionResponseCallback: Function; /** * Schema fields array @@ -158,3 +164,25 @@ export function replaceValueRecursive(document: any, replaceMap: Map { + const result = new Promise((resolve, reject) => { this.tasksCallbacks.set(taskId, { task, + number: 0, callback: (data, error) => { if (error) { + if (this.tasksCallbacks.has(taskId)) { + const callback = this.tasksCallbacks.get(taskId); + callback.number++; + if (callback.number > this.maxRepetitions) { + this.tasksCallbacks.delete(taskId); + reject(error); + return; + } + } this.queue.push(task); - reject(error); } else { + this.tasksCallbacks.delete(taskId); resolve(data); } - this.tasksCallbacks.delete(taskId); } }); }); - + this.channel.publish(WorkerEvents.QUEUE_UPDATED, null); + return result; } /** @@ -66,14 +80,12 @@ export class Workers extends ServiceRequestsBase { } const item = this.queue[itemIndex]; this.queue.splice(itemIndex, 1); - return new MessageResponse(item || null); }); this.channel.response(WorkerEvents.TASK_COMPLETE, async (msg: any) => { const activeTask = this.tasksCallbacks.get(msg.id); activeTask.callback(msg.data, msg.error); - this.tasksCallbacks.delete(msg.id); return new MessageResponse(null); }); } diff --git a/guardian-service/src/policy-engine/blocks/aggregate-block.ts b/guardian-service/src/policy-engine/blocks/aggregate-block.ts index 9adbc0fdd3..7ac24c5811 100644 --- a/guardian-service/src/policy-engine/blocks/aggregate-block.ts +++ b/guardian-service/src/policy-engine/blocks/aggregate-block.ts @@ -24,6 +24,7 @@ import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about children: ChildrenType.None, control: ControlType.Server, input: [ + PolicyInputEventType.PopEvent, PolicyInputEventType.RunEvent, PolicyInputEventType.TimerEvent, ], @@ -35,6 +36,36 @@ import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about } }) export class AggregateBlock { + /** + * Tick cron + * @event PolicyEventType.PopEvent + * @param {IPolicyEvent} event + */ + @ActionCallback({ + type: PolicyInputEventType.PopEvent + }) + public async onPopEvent(event: IPolicyEvent) { + const ref = PolicyComponentsUtils.GetBlockRef(this); + const docs: IPolicyDocument | IPolicyDocument[] = event.data.data; + if (Array.isArray(docs)) { + for (const doc of docs) { + await this.popDocuments(ref, doc); + } + } else { + await this.popDocuments(ref, docs); + } + } + + /** + * Pop documents + * @param ref + * @param doc + */ + async popDocuments(ref: AnyBlockType, doc: IPolicyDocument): Promise { + const hash = doc.hash; + await ref.databaseServer.removeAggregateDocument(hash, ref.uuid); + } + /** * Tick cron * @event PolicyEventType.TimerEvent diff --git a/guardian-service/src/policy-engine/blocks/calculate-block.ts b/guardian-service/src/policy-engine/blocks/calculate-block.ts index d55fe53816..8a0bf4712c 100644 --- a/guardian-service/src/policy-engine/blocks/calculate-block.ts +++ b/guardian-service/src/policy-engine/blocks/calculate-block.ts @@ -141,6 +141,8 @@ export class CalculateContainerBlock { item.type = outputSchema.iri; item.schema = outputSchema.iri; item.relationships = relationships.length ? relationships : null; + const accounts = isArray ? documents.reduce((a: any, b: any) => Object.assign(a, b.accounts), {}) : documents.accounts; + item.accounts = accounts && Object.keys(accounts).length ? accounts : null; // --> return item; diff --git a/guardian-service/src/policy-engine/blocks/custom-logic-block.ts b/guardian-service/src/policy-engine/blocks/custom-logic-block.ts index aa73988ff4..b141a74e2c 100644 --- a/guardian-service/src/policy-engine/blocks/custom-logic-block.ts +++ b/guardian-service/src/policy-engine/blocks/custom-logic-block.ts @@ -3,12 +3,15 @@ import { CatchErrors } from '@policy-engine/helpers/decorators/catch-errors'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { IPolicyCalculateBlock, IPolicyDocument, IPolicyEventState } from '@policy-engine/policy-engine.interface'; import { VcHelper } from '@helpers/vc-helper'; -import { SchemaHelper } from '@guardian/interfaces'; +import { GenerateUUIDv4, SchemaHelper } from '@guardian/interfaces'; import * as mathjs from 'mathjs'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces'; import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about'; import { IPolicyUser } from '@policy-engine/policy-user'; import { PolicyUtils } from '@policy-engine/helpers/utils'; +import { DIDDocument, DIDMessage, MessageAction, MessageServer } from '@hedera-modules'; +import { KeyType } from '@helpers/wallet'; +import { BlockActionError } from '@policy-engine/errors'; /** * Custom logic block @@ -70,6 +73,7 @@ export class CustomLogicBlock { execute(state: IPolicyEventState, user: IPolicyUser): Promise { return new Promise((resolve, reject) => { const ref = PolicyComponentsUtils.GetBlockRef(this); + const idType = ref.options.idType; let documents: IPolicyDocument[] = null; if (Array.isArray(state.data)) { documents = state.data; @@ -79,29 +83,53 @@ export class CustomLogicBlock { const done = async (result) => { try { - const root = await PolicyUtils.getHederaAccount(ref, ref.policyOwner); + const owner = PolicyUtils.getDocumentOwner(ref, documents[0]); + const hederaAccount = await PolicyUtils.getHederaAccount(ref, user.did); + let root; + switch(ref.options.documentSigner) { + case 'owner': + root = await PolicyUtils.getHederaAccount(ref, owner.did); + break; + case 'issuer': + const issuer = PolicyUtils.getDocumentIssuer(documents[0].document); + root = await PolicyUtils.getHederaAccount(ref, issuer); + break; + default: + root = await PolicyUtils.getHederaAccount(ref, ref.policyOwner); + break; + } const outputSchema = await ref.databaseServer.getSchemaByIRI(ref.options.outputSchema, ref.topicId); const context = SchemaHelper.getContext(outputSchema); const relationships = documents.filter(d => !!d.messageId).map(d => d.messageId); + const accounts = documents.reduce((a: any, b: any) => Object.assign(a, b.accounts), {}); const VCHelper = new VcHelper(); - const owner = PolicyUtils.getDocumentOwner(ref, documents[0]); const processing = async (document) => { + const vcSubject = { + id: idType === 'DOCUMENT' + ? documents[0].document.id + : await this.generateId( + idType, user, hederaAccount.hederaAccountId, hederaAccount.hederaAccountKey + ), + ...context, + ...document, + policyId: ref.policyId + }; + if (ref.dryRun) { + VCHelper.addDryRunContext(vcSubject); + } const newVC = await VCHelper.createVC( - ref.policyOwner, + root.did, root.hederaAccountKey, - { - ...context, - ...document, - policyId: ref.policyId - } + vcSubject ); const item = PolicyUtils.createVC(ref, owner, newVC); item.type = outputSchema.iri; item.schema = outputSchema.iri; item.relationships = relationships.length ? relationships : null; + item.accounts = Object.keys(accounts).length ? accounts : null;; return item; } @@ -126,4 +154,52 @@ export class CustomLogicBlock { func.apply(documents, [done, user, documents, mathjs]); }); } + + /** + * Generate id + * @param idType + * @param user + * @param userHederaAccount + * @param userHederaKey + */ + async generateId(idType: string, user: IPolicyUser, userHederaAccount: string, userHederaKey: string): Promise { + const ref = PolicyComponentsUtils.GetBlockRef(this); + try { + if (idType === 'UUID') { + return GenerateUUIDv4(); + } + if (idType === 'DID') { + const topic = await PolicyUtils.getOrCreateTopic(ref, 'root', null, null); + + const didObject = DIDDocument.create(null, topic.topicId); + const did = didObject.getDid(); + const key = didObject.getPrivateKeyString(); + const document = didObject.getDocument(); + + const message = new DIDMessage(MessageAction.CreateDID); + message.setDocument(didObject); + + const client = new MessageServer(userHederaAccount, userHederaKey, ref.dryRun); + const messageResult = await client + .setTopicObject(topic) + .sendMessage(message); + + const item = PolicyUtils.createDID(ref, user, did, document); + item.messageId = messageResult.getId(); + item.topicId = messageResult.getTopicId(); + + await ref.databaseServer.saveDid(item); + + await PolicyUtils.setAccountKey(ref, user.did, KeyType.KEY, did, key); + return did; + } + if (idType === 'OWNER') { + return user.did; + } + return undefined; + } catch (error) { + ref.error(`generateId: ${idType} : ${PolicyUtils.getErrorMessage(error)}`); + throw new BlockActionError(error, ref.blockType, ref.uuid); + } + } } diff --git a/guardian-service/src/policy-engine/blocks/documents-source-addon.ts b/guardian-service/src/policy-engine/blocks/documents-source-addon.ts index f07e14076b..a3b2461c72 100644 --- a/guardian-service/src/policy-engine/blocks/documents-source-addon.ts +++ b/guardian-service/src/policy-engine/blocks/documents-source-addon.ts @@ -111,7 +111,7 @@ export class DocumentsSourceAddon { } const dynFilters = {}; - for (const [key, value] of Object.entries(ref.getFilters(user))) { + for (const [key, value] of Object.entries(await ref.getFilters(user))) { dynFilters[key] = { $eq: value }; } diff --git a/guardian-service/src/policy-engine/blocks/documents-source.ts b/guardian-service/src/policy-engine/blocks/documents-source.ts index c56964eb08..8d3849fc1d 100644 --- a/guardian-service/src/policy-engine/blocks/documents-source.ts +++ b/guardian-service/src/policy-engine/blocks/documents-source.ts @@ -34,21 +34,21 @@ export class InterfaceDocumentsSource { * Block state field * @private */ - @StateField() - private state; + @StateField() + private state; - constructor() { - if (!this.state) { - this.state = {}; - } - } + constructor() { + if (!this.state) { + this.state = {}; + } + } /** * Set block data * @param user * @param data */ - public async setData(user: IPolicyUser, data: any): Promise { + public async setData(user: IPolicyUser, data: any): Promise { const oldState = this.state || {}; oldState[user.id] = data; this.state = oldState; @@ -92,9 +92,13 @@ export class InterfaceDocumentsSource { } const sortState = this.state[user.id] || {}; - const data: any = ref.options.uiMetaData.enableSorting + let data: any = ref.options.uiMetaData.enableSorting ? await this.getDataByAggregationFilters(ref, user, sortState, paginationData) - :await ref.getGlobalSources(user, paginationData); + : await ref.getGlobalSources(user, paginationData); + + for (const child of ref.children) { + data = await child.joinData(data, user, ref); + } return Object.assign({ data, @@ -163,7 +167,7 @@ export class InterfaceDocumentsSource { sortObject[sortState.orderField] = -1; break; default: - sortObject[sortState.orderField]= 1; + sortObject[sortState.orderField] = 1; break; } aggregation.push({ @@ -175,9 +179,9 @@ export class InterfaceDocumentsSource { aggregation.push({ $skip: paginationData.itemsPerPage * paginationData.page }, - { - $limit: paginationData.itemsPerPage - }) + { + $limit: paginationData.itemsPerPage + }) } switch (filtersAndDataType.dataType) { diff --git a/guardian-service/src/policy-engine/blocks/filters-addon-block.ts b/guardian-service/src/policy-engine/blocks/filters-addon-block.ts index c12a8d671d..2ee28dad3c 100644 --- a/guardian-service/src/policy-engine/blocks/filters-addon-block.ts +++ b/guardian-service/src/policy-engine/blocks/filters-addon-block.ts @@ -39,12 +39,21 @@ export class FiltersAddonBlock { * Get filters * @param user */ - public getFilters(user: IPolicyUser): { [key: string]: string } { + public async getFilters(user: IPolicyUser): Promise<{ [key: string]: string }> { const ref = PolicyComponentsUtils.GetBlockRef(this); const filters = ref.filters[user.id] || {}; if (ref.options.type === 'dropdown') { if (!filters[ref.options.field] && !ref.options.canBeEmpty) { - filters[ref.options.field] = ''; + const data: any[] = await ref.getSources(user, null); + const filterValue = findOptions(data[0], ref.options.optionValue); + if (filterValue) { + const blockState = this.state[user.id] || {}; + blockState.lastValue = filterValue; + this.state[user.id] = blockState; + filters[ref.options.field] = filterValue; + } else { + filters[ref.options.field] = ''; + } } } return filters; diff --git a/guardian-service/src/policy-engine/blocks/group-manager.ts b/guardian-service/src/policy-engine/blocks/group-manager.ts index a2f574acf4..a6bc410426 100644 --- a/guardian-service/src/policy-engine/blocks/group-manager.ts +++ b/guardian-service/src/policy-engine/blocks/group-manager.ts @@ -4,7 +4,7 @@ import { IPolicyInterfaceBlock } from '@policy-engine/policy-engine.interface'; import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about'; import { PolicyInputEventType } from '@policy-engine/interfaces'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; -import { IPolicyUser } from '@policy-engine/policy-user'; +import { IPolicyUser, PolicyUser } from '@policy-engine/policy-user'; import { PolicyRoles } from '@entity/policy-roles'; import { MessageServer, MessageStatus } from '@hedera-modules'; import { PolicyUtils } from '@policy-engine/helpers/utils'; @@ -99,6 +99,12 @@ export class GroupManagerBlock { } else { throw new Error(`Permission denied`); } + } else if(member.groupAccessType === GroupAccessType.Global) { + if (ref.options.canDelete === 'all' || member.owner === user.did) { + await ref.databaseServer.deleteGroup(member); + } else { + throw new Error(`Permission denied`); + } } else { throw new Error(`Invalid Group type`); } @@ -113,6 +119,8 @@ export class GroupManagerBlock { .setTopicObject(topic) .sendMessage(message, false); } + + ref.triggerInternalEvent('remove-user', (new PolicyUser(did, !!ref.dryRun)).setGroup(member)); } /** diff --git a/guardian-service/src/policy-engine/blocks/index.ts b/guardian-service/src/policy-engine/blocks/index.ts index 78890a7260..37e6744621 100644 --- a/guardian-service/src/policy-engine/blocks/index.ts +++ b/guardian-service/src/policy-engine/blocks/index.ts @@ -29,4 +29,5 @@ export { ButtonBlock } from './button-block'; export { TokenActionBlock } from './token-action-block'; export { DocumentValidatorBlock } from './document-validator-block'; export { TokenConfirmationBlock } from './token-confirmation-block'; -export { GroupManagerBlock } from './group-manager'; \ No newline at end of file +export { GroupManagerBlock } from './group-manager'; +export { MultiSignBlock } from './multi-sign-block'; \ No newline at end of file diff --git a/guardian-service/src/policy-engine/blocks/multi-sign-block.ts b/guardian-service/src/policy-engine/blocks/multi-sign-block.ts new file mode 100644 index 0000000000..01ef3422fb --- /dev/null +++ b/guardian-service/src/policy-engine/blocks/multi-sign-block.ts @@ -0,0 +1,347 @@ +import { ActionCallback, EventBlock } from '@policy-engine/helpers/decorators'; +import { PolicyValidationResultsContainer } from '@policy-engine/policy-validation-results-container'; +import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; +import { DataTypes, PolicyUtils } from '@policy-engine/helpers/utils'; +import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces'; +import { ChildrenType, ControlType, PropertyType } from '@policy-engine/interfaces/block-about'; +import { AnyBlockType, IPolicyDocument, IPolicyEventState } from '@policy-engine/policy-engine.interface'; +import { IPolicyUser } from '@policy-engine/policy-user'; +import { BlockActionError } from '@policy-engine/errors'; +import { VcHelper } from '@helpers/vc-helper'; +import { Inject } from '@helpers/decorators/inject'; +import { GenerateUUIDv4 } from '@guardian/interfaces'; +import { MessageAction, MessageServer, VcDocument, VPMessage } from '@hedera-modules'; +import { PolicyRoles } from '@entity/policy-roles'; +import { VcDocument as VcDocumentCollection } from '@entity/vc-document'; + +/** + * Sign Status + */ +enum DocumentStatus { + NEW = 'NEW', + SIGNED = 'SIGNED', + DECLINED = 'DECLINED', +} + +/** + * Switch block + */ +@EventBlock({ + blockType: 'multiSignBlock', + commonBlock: true, + about: { + label: 'Multiple Signature', + title: `Add 'Multiple Signature' Block`, + post: true, + get: true, + children: ChildrenType.None, + control: ControlType.UI, + input: [ + PolicyInputEventType.RunEvent, + ], + output: [ + PolicyOutputEventType.RefreshEvent, + PolicyOutputEventType.SignatureQuorumReachedEvent, + PolicyOutputEventType.SignatureSetInsufficientEvent + ], + defaultEvent: false, + properties: [{ + name: 'threshold', + label: 'Threshold (%)', + title: 'Number of signatures required to move to the next step, as a percentage of the total number of users in the group.', + type: PropertyType.Input, + default: '50' + }] + } +}) +export class MultiSignBlock { + /** + * VC helper + * @private + */ + @Inject() + private readonly vcHelper: VcHelper; + + /** + * Before init callback + */ + public async beforeInit(): Promise { + const ref = PolicyComponentsUtils.GetBlockRef(this); + ref.addInternalListener('remove-user', this.onRemoveUser.bind(this)); + } + + /** + * Join GET Data + * @param {IPolicyDocument | IPolicyDocument[]} documents + * @param {IPolicyUser} user + * @param {AnyBlockType} parent + */ + public async joinData( + documents: T, user: IPolicyUser, parent: AnyBlockType + ): Promise { + const ref = PolicyComponentsUtils.GetBlockRef(this); + const getData = await this.getData(user); + if (Array.isArray(documents)) { + for (const doc of documents) { + if (!doc.blocks) { + doc.blocks = {}; + } + const status = await this.getDocumentStatus(doc, user); + doc.blocks[ref.uuid] = { ...getData, status }; + } + } else { + if (!documents.blocks) { + documents.blocks = {}; + } + const status = await this.getDocumentStatus(documents, user); + documents.blocks[ref.uuid] = { ...getData, status }; + } + return documents; + } + + /** + * Get block data + * @param user + */ + async getData(user: IPolicyUser): Promise { + const ref = PolicyComponentsUtils.GetBlockRef(this); + const data: any = { + id: ref.uuid, + blockType: ref.blockType, + type: ref.options.type, + uiMetaData: ref.options.uiMetaData, + user: ref.options.user + } + return data; + } + + /** + * Set block data + * @param user + * @param blockData + */ + async setData(user: IPolicyUser, blockData: any): Promise { + const ref = PolicyComponentsUtils.GetBlockRef(this); + const { status, document } = blockData; + const documentId = document.id; + const sourceDoc = await ref.databaseServer.getVcDocument(documentId); + + if (!sourceDoc) { + throw new BlockActionError(`Invalid document`, ref.blockType, ref.uuid); + } + + const confirmationStatus = await ref.databaseServer.getMultiSignStatus(ref.uuid, documentId); + if (confirmationStatus) { + if (confirmationStatus.status !== DocumentStatus.NEW) { + throw new BlockActionError('The document has already been signed', ref.blockType, ref.uuid); + } + } else { + await ref.databaseServer.setMultiSigStatus(ref.uuid, documentId, user.group, DocumentStatus.NEW); + } + const documentStatus = await ref.databaseServer.getMultiSignStatus(ref.uuid, documentId, user.id); + if (documentStatus) { + throw new BlockActionError('The document has already been signed', ref.blockType, ref.uuid); + } + + const root = await PolicyUtils.getHederaAccount(ref, user.did); + const groupContext = await PolicyUtils.getGroupContext(ref, user); + const vcDocument = sourceDoc.document; + const credentialSubject = vcDocument.credentialSubject[0]; + const newVC = await this.vcHelper.createVC( + root.did, + root.hederaAccountKey, + credentialSubject, + groupContext + ); + + await ref.databaseServer.setMultiSigDocument( + ref.uuid, + documentId, + user, + status === DocumentStatus.SIGNED ? DocumentStatus.SIGNED : DocumentStatus.DECLINED, + newVC.toJsonTree() + ); + + const users = await ref.databaseServer.getAllUsersByRole(ref.policyId, user.group, user.role); + await this.updateThreshold(users, sourceDoc, documentId, user); + + ref.triggerEvents(PolicyOutputEventType.RefreshEvent, user, null); + } + + /** + * Check threshold + * @param users + * @param sourceDoc + * @param documentId + * @param currentUser + */ + private async updateThreshold( + users: PolicyRoles[], + sourceDoc: VcDocumentCollection, + documentId: string, + currentUser: IPolicyUser + ) { + const ref = PolicyComponentsUtils.GetBlockRef(this); + const data = await ref.databaseServer.getMultiSignDocuments(ref.uuid, documentId, currentUser.group); + + let signed = 0; + let declined = 0; + for (const u of data) { + if (u.status === DocumentStatus.SIGNED) { + signed++; + } else if (u.status === DocumentStatus.DECLINED) { + declined++; + } + } + + const signedThreshold = Math.ceil(users.length * ref.options.threshold / 100); + const declinedThreshold = Math.round(users.length - signedThreshold + 1); + + if (signed >= signedThreshold) { + const docOwner = PolicyUtils.getDocumentOwner(ref, sourceDoc); + const policyOwnerAccount = await PolicyUtils.getHederaAccount(ref, ref.policyOwner); + const documentOwnerAccount = await PolicyUtils.getHederaAccount(ref, docOwner.did); + + const vcs = data.map(e => VcDocument.fromJsonTree(e.document)); + const vp = await this.vcHelper.createVP( + policyOwnerAccount.did, + policyOwnerAccount.hederaAccountKey, + vcs, + GenerateUUIDv4() + ); + + const vpMessage = new VPMessage(MessageAction.CreateVP); + vpMessage.setDocument(vp); + vpMessage.setRelationships(sourceDoc.messageId ? [sourceDoc.messageId] : []); + const topic = await PolicyUtils.getTopicById(ref, sourceDoc.topicId); + const messageServer = new MessageServer( + documentOwnerAccount.hederaAccountId, + documentOwnerAccount.hederaAccountKey, + ref.dryRun + ); + + const vpMessageResult = await messageServer + .setTopicObject(topic) + .sendMessage(vpMessage); + const vpMessageId = vpMessageResult.getId(); + const vpDocument = PolicyUtils.createVP(ref, docOwner, vp); + vpDocument.type = DataTypes.MULTI_SIGN; + vpDocument.messageId = vpMessageId; + vpDocument.topicId = vpMessageResult.getTopicId(); + await ref.databaseServer.saveVP(vpDocument); + + await ref.databaseServer.setMultiSigStatus(ref.uuid, documentId, currentUser.group, DocumentStatus.SIGNED); + + ref.triggerEvents(PolicyOutputEventType.SignatureQuorumReachedEvent, currentUser, { data: sourceDoc }); + } else if (declined >= declinedThreshold) { + await ref.databaseServer.setMultiSigStatus(ref.uuid, documentId, currentUser.group, DocumentStatus.DECLINED); + ref.triggerEvents(PolicyOutputEventType.SignatureSetInsufficientEvent, currentUser, { data: sourceDoc }); + } + } + + /** + * Get Document Status + * @param {IPolicyDocument} document + * @param {IPolicyUser} user + */ + private async getDocumentStatus(document: IPolicyDocument, user: IPolicyUser) { + const ref = PolicyComponentsUtils.GetBlockRef(this); + const confirmationDocument = await ref.databaseServer.getMultiSignStatus(ref.uuid, document.id); + const data: any[] = await ref.databaseServer.getMultiSignDocuments(ref.uuid, document.id, user.group); + const users = await ref.databaseServer.getAllUsersByRole(ref.policyId, user.group, user.role); + + let signed = 0; + let declined = 0; + let documentStatus = DocumentStatus.NEW; + for (const u of data) { + if (u.userId === user.id) { + documentStatus = u.status; + } + if (u.status === DocumentStatus.SIGNED) { + signed++; + } + if (u.status === DocumentStatus.DECLINED) { + declined++; + } + } + + let confirmationStatus: string = null; + if (confirmationDocument && confirmationDocument.status !== DocumentStatus.NEW) { + confirmationStatus = confirmationDocument.status; + } + + const threshold = ref.options.threshold; + const total = users.length; + const signedThreshold = Math.ceil(users.length * threshold / 100); + const declinedThreshold = Math.round(users.length - signedThreshold + 1); + const result = { + documentStatus, + confirmationStatus, + data, + total, + signedCount: signed, + signedPercent: (signed / total) * 100, + declinedCount: declined, + declinedPercent: (declined / total) * 100, + threshold, + signedThreshold, + declinedThreshold + } + + return result; + } + + /** + * Remove User Event + * @param {IPolicyUser} user + */ + private async onRemoveUser(user: IPolicyUser) { + const ref = PolicyComponentsUtils.GetBlockRef(this); + if (user) { + const users = await ref.databaseServer.getAllUsersByRole(ref.policyId, user.group, user.role); + const documents = await ref.databaseServer.getMultiSignDocumentsByGroup(ref.uuid, user.group); + for (const document of documents) { + const documentId = document.documentId; + const vc = await ref.databaseServer.getVcDocument(documentId); + await this.updateThreshold(users, vc, documentId, user); + } + ref.triggerEvents(PolicyOutputEventType.RefreshEvent, null, null); + } + } + + /** + * Run block action + * @event PolicyEventType.Run + * @param {IPolicyEvent} event + */ + @ActionCallback({ + output: [PolicyOutputEventType.RunEvent, PolicyOutputEventType.RefreshEvent] + }) + async runAction(event: IPolicyEvent) { + return; + } + + /** + * Validate block options + * @param resultsContainer + */ + public async validate(resultsContainer: PolicyValidationResultsContainer): Promise { + const ref = PolicyComponentsUtils.GetBlockRef(this); + try { + if (!ref.options.threshold) { + resultsContainer.addBlockError(ref.uuid, 'Option "threshold" does not set'); + } else { + try { + const t = parseFloat(ref.options.threshold); + if (t < 0 || t > 100) { + resultsContainer.addBlockError(ref.uuid, '"threshold" value must be between 0 and 100'); + } + } catch (error) { + resultsContainer.addBlockError(ref.uuid, 'Option "threshold" must be a number'); + } + } + } catch (error) { + resultsContainer.addBlockError(ref.uuid, `Unhandled exception ${PolicyUtils.getErrorMessage(error)}`); + } + } +} diff --git a/guardian-service/src/policy-engine/blocks/policy-roles.ts b/guardian-service/src/policy-engine/blocks/policy-roles.ts index aab8cb1c8c..8f1777d41a 100644 --- a/guardian-service/src/policy-engine/blocks/policy-roles.ts +++ b/guardian-service/src/policy-engine/blocks/policy-roles.ts @@ -356,9 +356,22 @@ export class PolicyRolesBlock { */ async getData(user: IPolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const roles: string[] = Array.isArray(ref.options.roles) ? ref.options.roles : []; + const groups: string[] = Array.isArray(ref.options.groups) ? ref.options.groups : []; + const policyGroups: IGroupConfig[] = ref.policyInstance.policyGroups || []; + const groupMap = {}; + for (const item of policyGroups) { + if (groups.indexOf(item.name) > -1) { + groupMap[item.name] = { + groupAccessType: item.groupAccessType, + groupRelationshipType: item.groupRelationshipType + }; + } + } return { - roles: Array.isArray(ref.options.roles) ? ref.options.roles : [], - groups: Array.isArray(ref.options.groups) ? ref.options.groups : [], + roles, + groups, + groupMap, isMultipleGroups: ref.isMultipleGroups, uiMetaData: ref.options.uiMetaData } @@ -395,6 +408,10 @@ export class PolicyRolesBlock { } else { throw new BlockActionError('Invalid role', ref.blockType, ref.uuid); } + const ifUserGroup = await ref.databaseServer.checkUserInGroup(group); + if (ifUserGroup) { + throw new BlockActionError('You are already a member of the group', ref.blockType, ref.uuid); + } group.messageId = await this.createVC(ref, user, group); diff --git a/guardian-service/src/policy-engine/blocks/reassigning.block.ts b/guardian-service/src/policy-engine/blocks/reassigning.block.ts index 0732309ec9..617a9cc68a 100644 --- a/guardian-service/src/policy-engine/blocks/reassigning.block.ts +++ b/guardian-service/src/policy-engine/blocks/reassigning.block.ts @@ -8,6 +8,7 @@ import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '@poli import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about'; import { IPolicyUser } from '@policy-engine/policy-user'; import { PolicyUtils } from '@policy-engine/helpers/utils'; +import { IRootConfig } from '@guardian/interfaces'; /** * Reassigning block @@ -59,7 +60,7 @@ export class ReassigningBlock { const vcDocument = document.document; const owner: IPolicyUser = PolicyUtils.getDocumentOwner(ref, document); - let root: any; + let root: IRootConfig; let groupContext: any; if (ref.options.issuer === 'owner') { root = await PolicyUtils.getHederaAccount(ref, document.owner); diff --git a/guardian-service/src/policy-engine/blocks/report-block.ts b/guardian-service/src/policy-engine/blocks/report-block.ts index fcc4dfeaaa..ce9f8b23e8 100644 --- a/guardian-service/src/policy-engine/blocks/report-block.ts +++ b/guardian-service/src/policy-engine/blocks/report-block.ts @@ -75,12 +75,18 @@ export class ReportBlock { * @param documents * @param map */ - async itemUserMap(documents: IReportItem[], map) { + async itemUserMap(documents: any[], map) { if (!documents) { return; } for (const element of documents) { - element.username = await this.getUserName(element.username, map); + if (element.multiple) { + for (const document of element.document) { + document.username = await this.getUserName(document.username, map); + } + } else { + element.username = await this.getUserName(element.username, map); + } await this.itemUserMap(element.documents, map); } } diff --git a/guardian-service/src/policy-engine/blocks/report-item-block.ts b/guardian-service/src/policy-engine/blocks/report-item-block.ts index 34dc51aad0..6896e0b9bb 100644 --- a/guardian-service/src/policy-engine/blocks/report-item-block.ts +++ b/guardian-service/src/policy-engine/blocks/report-item-block.ts @@ -37,19 +37,8 @@ export class ReportItemBlock { const description = ref.options.description; const visible = ref.options.visible; const iconType = ref.options.iconType; - const item: IReportItem = { - type: 'VC', - icon, - title, - description, - visible, - tag: null, - issuer: null, - username: null, - document: null, - iconType - } - resultFields.push(item); + const multiple = ref.options.multiple; + const dynamicFilters = ref.options.dynamicFilters; const filtersToVc:any = {}; if (ref.options.filters) { @@ -93,17 +82,53 @@ export class ReportItemBlock { } filtersToVc.policyId = { $eq: ref.policyId }; - const vcDocument = await ref.databaseServer.getVcDocument(filtersToVc); + const item: any = { + type: 'VC', + icon, + title, + description, + visible, + iconType, + multiple, + dynamicFilters + } + resultFields.push(item); + + const vcDocuments: any = multiple + ? await ref.databaseServer.getVcDocuments(filtersToVc) + : [await ref.databaseServer.getVcDocument(filtersToVc)]; - if (vcDocument) { - item.tag = vcDocument.tag; - item.issuer = getVCIssuer(vcDocument); - item.username = getVCIssuer(vcDocument); - item.document = vcDocument; + for (const vcDocument of vcDocuments) { + if (vcDocument) { + if (multiple) { + item.document = item.document || []; + item.document.push({ + tag: vcDocument.tag, + issuer: getVCIssuer(vcDocument), + username: getVCIssuer(vcDocument), + document: vcDocument + }); + } else { + item.tag = vcDocument.tag; + item.issuer = getVCIssuer(vcDocument); + item.username = getVCIssuer(vcDocument); + item.document = vcDocument; + } - if (ref.options.variables) { - for (const variable of ref.options.variables) { - variables[variable.name] = findOptions(vcDocument, variable.value); + if (ref.options.variables) { + for (const variable of ref.options.variables) { + const findOptionsResult = findOptions(vcDocument, variable.value); + if (multiple) { + variables[variable.name] = variables[variable.name] || [] + if (Array.isArray(findOptionsResult)) { + variables[variable.name].push(...findOptionsResult); + } else { + variables[variable.name].push(findOptionsResult); + } + } else { + variables[variable.name] = findOptionsResult; + } + } } } } diff --git a/guardian-service/src/policy-engine/blocks/retirement-block.ts b/guardian-service/src/policy-engine/blocks/retirement-block.ts index 00794e71af..fc1d9c6965 100644 --- a/guardian-service/src/policy-engine/blocks/retirement-block.ts +++ b/guardian-service/src/policy-engine/blocks/retirement-block.ts @@ -1,6 +1,6 @@ import { ActionCallback, BasicBlock } from '@policy-engine/helpers/decorators'; import { BlockActionError } from '@policy-engine/errors'; -import { DocumentSignature, GenerateUUIDv4, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; +import { DocumentSignature, GenerateUUIDv4, IRootConfig, SchemaEntity, SchemaHelper } from '@guardian/interfaces'; import { PolicyValidationResultsContainer } from '@policy-engine/policy-validation-results-container'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { CatchErrors } from '@policy-engine/helpers/decorators/catch-errors'; @@ -46,7 +46,7 @@ export class RetirementBlock { * @param ref * @private */ - private async createWipeVC(root: any, token: any, data: any, ref: AnyBlockType): Promise { + private async createWipeVC(root: IRootConfig, token: any, data: any, ref: AnyBlockType): Promise { const vcHelper = new VcHelper(); const policySchema = await ref.databaseServer.getSchemaByType(ref.topicId, SchemaEntity.WIPE_TOKEN); const amount = data as string; @@ -67,7 +67,7 @@ export class RetirementBlock { * @param vcs * @private */ - private async createVP(root: any, uuid: string, vcs: VcDocument[]) { + private async createVP(root: IRootConfig, uuid: string, vcs: VcDocument[]) { const vcHelper = new VcHelper(); const vp = await vcHelper.createVP( root.did, @@ -93,7 +93,7 @@ export class RetirementBlock { documents: VcDocument[], relationships: string[], topicId: string, - root: any, + root: IRootConfig, user: IPolicyUser, targetAccountId: string ): Promise { diff --git a/guardian-service/src/policy-engine/blocks/revoke-block.ts b/guardian-service/src/policy-engine/blocks/revoke-block.ts index 38d9590811..2a7ba8a8fe 100644 --- a/guardian-service/src/policy-engine/blocks/revoke-block.ts +++ b/guardian-service/src/policy-engine/blocks/revoke-block.ts @@ -194,18 +194,6 @@ export class RevokeBlock { return; } - if (!ref.options.uiMetaData.name) { - resultsContainer.addBlockError(ref.uuid, 'Option "Button Name" does not set'); - } - - if (!ref.options.uiMetaData.title) { - resultsContainer.addBlockError(ref.uuid, 'Option "Title" does not set'); - } - - if (!ref.options.uiMetaData.description) { - resultsContainer.addBlockError(ref.uuid, 'Option "Description" does not set'); - } - if (ref.options.uiMetaData.updatePrevDoc && !ref.options.uiMetaData.prevDocStatus) { resultsContainer.addBlockError(ref.uuid, 'Option "Status Value" does not set'); } diff --git a/guardian-service/src/policy-engine/blocks/set-relationships-block.ts b/guardian-service/src/policy-engine/blocks/set-relationships-block.ts index bd69ab0560..867748d813 100644 --- a/guardian-service/src/policy-engine/blocks/set-relationships-block.ts +++ b/guardian-service/src/policy-engine/blocks/set-relationships-block.ts @@ -2,7 +2,7 @@ import { ActionCallback, EventBlock } from '@policy-engine/helpers/decorators'; import { PolicyComponentsUtils } from '@policy-engine/policy-components-utils'; import { IPolicyDocument, IPolicyEventState, IPolicyRequestBlock } from '@policy-engine/policy-engine.interface'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '@policy-engine/interfaces'; -import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about'; +import { ChildrenType, ControlType, PropertyType } from '@policy-engine/interfaces/block-about'; /** * Set document relationships action @@ -23,7 +23,20 @@ import { ChildrenType, ControlType } from '@policy-engine/interfaces/block-about output: [ PolicyOutputEventType.RunEvent ], - defaultEvent: true + defaultEvent: true, + properties: [{ + name: 'includeAccounts', + label: 'Include Accounts', + title: 'Include Related Documents Accounts', + type: PropertyType.Checkbox, + default: false + }, { + name: 'changeOwner', + label: 'Change Owner', + title: 'Change Document Owner', + type: PropertyType.Checkbox, + default: false + }] } }) export class SetRelationshipsBlock { @@ -37,21 +50,48 @@ export class SetRelationshipsBlock { async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); const data: IPolicyDocument[] = await ref.getSources(event.user); + const owner = data[0] && data[0].owner || null; + const group = data[0] && data[0].group || null; + let accounts = {}; const relationships = []; for (const doc of data) { + accounts = Object.assign(accounts, doc.accounts); if (doc.messageId && !relationships.includes(doc.messageId)) { relationships.push(doc.messageId); } } + accounts = Object.keys(accounts).length ? accounts : null; const documents = event.data?.data; if (documents) { if (Array.isArray(documents)) { for (const doc of documents) { doc.relationships = doc.relationships ? doc.relationships.concat(relationships) : relationships; + if (doc.accounts && ref.options.includeAccounts) { + Object.assign(doc.accounts, accounts); + } else if (ref.options.includeAccounts) { + doc.accounts = accounts; + } + if (owner && ref.options.changeOwner) { + doc.owner = owner; + } + if (group && ref.options.changeOwner) { + doc.group = group; + } } } else { documents.relationships = documents.relationships ? documents.relationships.concat(relationships) : relationships; + if (documents.accounts && ref.options.includeAccounts) { + Object.assign(documents.accounts, accounts); + } else if (ref.options.includeAccounts) { + documents.accounts = accounts; + } + if (owner && ref.options.changeOwner) { + documents.owner = owner; + } + if (group && ref.options.changeOwner) { + documents.group = group; + } } } diff --git a/guardian-service/src/policy-engine/blocks/token-action-block.ts b/guardian-service/src/policy-engine/blocks/token-action-block.ts index f8c99185cb..bc51089779 100644 --- a/guardian-service/src/policy-engine/blocks/token-action-block.ts +++ b/guardian-service/src/policy-engine/blocks/token-action-block.ts @@ -74,7 +74,7 @@ export class TokenActionBlock { } } - PolicyUtils.checkAccountId(account); + await PolicyUtils.checkAccountId(account); switch (ref.options.action) { case 'associate': { @@ -154,4 +154,4 @@ export class TokenActionBlock { resultsContainer.addBlockError(ref.uuid, `Unhandled exception ${PolicyUtils.getErrorMessage(error)}`); } } -} \ No newline at end of file +} diff --git a/guardian-service/src/policy-engine/blocks/token-confirmation-block.ts b/guardian-service/src/policy-engine/blocks/token-confirmation-block.ts index fe2a8279d8..cd3fd6d8ae 100644 --- a/guardian-service/src/policy-engine/blocks/token-confirmation-block.ts +++ b/guardian-service/src/policy-engine/blocks/token-confirmation-block.ts @@ -127,7 +127,7 @@ export class TokenConfirmationBlock { const token = await this.getToken(); - PolicyUtils.checkAccountId(account); + await PolicyUtils.checkAccountId(account); switch (ref.options.action) { case 'associate': { @@ -220,4 +220,4 @@ export class TokenConfirmationBlock { resultsContainer.addBlockError(ref.uuid, `Unhandled exception ${PolicyUtils.getErrorMessage(error)}`); } } -} \ No newline at end of file +} diff --git a/guardian-service/src/policy-engine/helpers/decorators/basic-block.ts b/guardian-service/src/policy-engine/helpers/decorators/basic-block.ts index 121ca93b1a..0884ab667a 100644 --- a/guardian-service/src/policy-engine/helpers/decorators/basic-block.ts +++ b/guardian-service/src/policy-engine/helpers/decorators/basic-block.ts @@ -2,7 +2,7 @@ import { PolicyBlockDefaultOptions } from '@policy-engine/helpers/policy-block-d import { EventConfig } from '@policy-engine/interfaces'; import { PolicyBlockDecoratorOptions, PolicyBlockFullArgumentList } from '@policy-engine/interfaces/block-options'; import { ExternalMessageEvents, PolicyRole, PolicyType } from '@guardian/interfaces'; -import { AnyBlockType, IPolicyBlock, ISerializedBlock, } from '../../policy-engine.interface'; +import { AnyBlockType, IPolicyBlock, IPolicyDocument, ISerializedBlock, } from '../../policy-engine.interface'; import { PolicyComponentsUtils } from '../../policy-components-utils'; import { PolicyValidationResultsContainer } from '@policy-engine/policy-validation-results-container'; import { IPolicyEvent, PolicyLink } from '@policy-engine/interfaces/policy-event'; @@ -218,7 +218,7 @@ export function BasicBlock(options: Partial) { */ public async beforeInit(): Promise { if (typeof super.beforeInit === 'function') { - super.beforeInit(); + await super.beforeInit(); } } @@ -229,7 +229,7 @@ export function BasicBlock(options: Partial) { await this.restoreState(); if (typeof super.afterInit === 'function') { - super.afterInit(); + await super.afterInit(); } } @@ -358,6 +358,21 @@ export function BasicBlock(options: Partial) { this.updateBlock(event.data, event.user, ''); } + /** + * Join GET Data + * @param {IPolicyDocument | IPolicyDocument[]} data + * @param {IPolicyUser} user + * @param {AnyBlockType} parent + */ + public async joinData( + data: U, user: IPolicyUser, parent: AnyBlockType + ): Promise { + if (typeof super.joinData === 'function') { + return await super.joinData(data, user, parent); + } + return data; + } + /** * Update block * @param state @@ -368,16 +383,23 @@ export function BasicBlock(options: Partial) { await this.saveState(); const users: { [x: string]: IPolicyUser } = {}; if (!this.options.followUser) { - const allUsers = await this.databaseServer.getAllPolicyUsers(this.policyId); - for (const userRole of allUsers) { - if (this.permissions.includes(userRole.role)) { - users[userRole.did] = PolicyUser.create(userRole, !!this.dryRun); - } else if (this.permissions.includes('ANY_ROLE')) { - users[userRole.did] = PolicyUser.create(userRole, !!this.dryRun); + if (this.dryRun) { + const virtualUser = await DatabaseServer.getVirtualUser(this.policyId); + const group = await this.databaseServer.getActiveGroupByUser(this.policyId, virtualUser?.did); + users[virtualUser?.did] = (new PolicyUser(virtualUser?.did, !!this.dryRun)) + .setGroup(group); + } else { + const allUsers = await this.databaseServer.getAllPolicyUsers(this.policyId); + for (const userRole of allUsers) { + if (this.permissions.includes(userRole.role)) { + users[userRole.did] = PolicyUser.create(userRole, !!this.dryRun); + } else if (this.permissions.includes('ANY_ROLE')) { + users[userRole.did] = PolicyUser.create(userRole, !!this.dryRun); + } + } + if (this.permissions.includes('OWNER') || this.permissions.includes('ANY_ROLE')) { + users[this.policyOwner] = new PolicyUser(this.policyOwner); } - } - if (this.permissions.includes('OWNER') || this.permissions.includes('ANY_ROLE')) { - users[this.policyOwner] = new PolicyUser(this.policyOwner); } } else { users[user.did] = user; @@ -645,6 +667,25 @@ export function BasicBlock(options: Partial) { protected warn(message: string) { this.logger.warn(message, ['GUARDIAN_SERVICE', this.uuid, this.blockType, this.tag, this.policyId]); } + + /** + * Add Internal Event Listener + * @param type + * @protected + */ + protected addInternalListener(type: string, callback: Function) { + PolicyComponentsUtils.AddInternalListener(type, this.policyId, callback); + } + + /** + * Trigger Internal Event + * @param type + * @param data + * @protected + */ + protected triggerInternalEvent(type: string, data: any) { + PolicyComponentsUtils.TriggerInternalEvent(type, this.policyId, data); + } }; }; } diff --git a/guardian-service/src/policy-engine/helpers/decorators/data-source-addon.ts b/guardian-service/src/policy-engine/helpers/decorators/data-source-addon.ts index 6e09be3c54..5d394df0ce 100644 --- a/guardian-service/src/policy-engine/helpers/decorators/data-source-addon.ts +++ b/guardian-service/src/policy-engine/helpers/decorators/data-source-addon.ts @@ -29,7 +29,7 @@ export function DataSourceAddon(options: Partial) { * Get block filters * @param user */ - public getFilters(user: IPolicyUser): { [key: string]: string } { + public async getFilters(user: IPolicyUser): Promise<{ [key: string]: string }> { if (typeof super.getFilters === 'function') { return super.getFilters(user); } diff --git a/guardian-service/src/policy-engine/helpers/decorators/data-source-block.ts b/guardian-service/src/policy-engine/helpers/decorators/data-source-block.ts index 8c4e0d3ee7..c29f289fee 100644 --- a/guardian-service/src/policy-engine/helpers/decorators/data-source-block.ts +++ b/guardian-service/src/policy-engine/helpers/decorators/data-source-block.ts @@ -70,7 +70,7 @@ export function DataSourceBlock(options: Partial) { const dynFilters = {}; for (const child of this.children) { if (child.blockClassName === 'DataSourceAddon') { - for (const [key, value] of Object.entries(child.getFilters(user))) { + for (const [key, value] of Object.entries(await child.getFilters(user))) { dynFilters[key] = { $eq: value }; } } diff --git a/guardian-service/src/policy-engine/helpers/decorators/source-addon.ts b/guardian-service/src/policy-engine/helpers/decorators/source-addon.ts index c87ca87920..6a0dc3af12 100644 --- a/guardian-service/src/policy-engine/helpers/decorators/source-addon.ts +++ b/guardian-service/src/policy-engine/helpers/decorators/source-addon.ts @@ -67,11 +67,11 @@ export function SourceAddon(options: Partial) { * @param user * @protected */ - protected getFilters(user): { [key: string]: string } { + protected async getFilters(user): Promise<{ [key: string]: string }> { const filters = {}; for (const addon of this.getAddons()) { - Object.assign(filters, (addon as any).getFilters(user)); + Object.assign(filters, await (addon as any).getFilters(user)); } return filters; diff --git a/guardian-service/src/policy-engine/helpers/policy-import-export-helper.ts b/guardian-service/src/policy-engine/helpers/policy-import-export-helper.ts index a6df60b8cc..e98e4ac596 100644 --- a/guardian-service/src/policy-engine/helpers/policy-import-export-helper.ts +++ b/guardian-service/src/policy-engine/helpers/policy-import-export-helper.ts @@ -8,9 +8,9 @@ import { import JSZip from 'jszip'; import { Token } from '@entity/token'; import { Schema } from '@entity/schema'; -import { SchemaEntity, TopicType, GenerateUUIDv4 } from '@guardian/interfaces'; +import { SchemaEntity, TopicType, GenerateUUIDv4, WorkerTaskType } from '@guardian/interfaces'; import { Users } from '@helpers/users'; -import { HederaSDKHelper, MessageAction, MessageServer, MessageType, PolicyMessage, TopicHelper } from '@hedera-modules'; +import { MessageAction, MessageServer, MessageType, PolicyMessage, TopicHelper } from '@hedera-modules'; import { Topic } from '@entity/topic'; import { importSchemaByFiles, publishSystemSchema } from '@api/schema.service'; import { PrivateKey } from '@hashgraph/sdk'; @@ -18,6 +18,7 @@ import { PolicyConverterUtils } from '@policy-engine/policy-converter-utils'; import { INotifier } from '@helpers/notifier'; import { DatabaseServer } from '@database-modules'; import { DataBaseHelper } from '@guardian/common'; +import { Workers } from '@helpers/workers'; /** * Policy import export helper @@ -223,7 +224,6 @@ export class PolicyImportExportHelper { // Import Tokens if (tokens) { notifier.start('Import tokens'); - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey); const rootHederaAccountKey = PrivateKey.fromString(root.hederaAccountKey); const tokenRepository = new DataBaseHelper(Token); for (const token of tokens) { @@ -233,28 +233,32 @@ export class PolicyImportExportHelper { const nft = tokenType === 'non-fungible'; const decimals = nft ? 0 : token.decimals; const initialSupply = nft ? 0 : token.initialSupply; - const adminKey = token.adminKey ? rootHederaAccountKey : null; - const kycKey = token.kycKey ? rootHederaAccountKey : null; - const freezeKey = token.freezeKey ? rootHederaAccountKey : null; - const wipeKey = token.wipeKey ? rootHederaAccountKey : null; - const supplyKey = token.supplyKey ? rootHederaAccountKey : null; - const tokenId = await client.newToken( - tokenName, - tokenSymbol, - nft, - decimals, - initialSupply, - '', - { - id: root.hederaAccountId, - key: rootHederaAccountKey - }, - adminKey, - kycKey, - freezeKey, - wipeKey, - supplyKey, - ); + const adminKey = token.adminKey ? rootHederaAccountKey.toString() : null; + const kycKey = token.kycKey ? rootHederaAccountKey.toString() : null; + const freezeKey = token.freezeKey ? rootHederaAccountKey.toString() : null; + const wipeKey = token.wipeKey ? rootHederaAccountKey.toString() : null; + const supplyKey = token.supplyKey ? rootHederaAccountKey.toString() : null; + + const workers = new Workers(); + const tokenId = await workers.addTask({ + type: WorkerTaskType.NEW_TOKEN, + data: { + operatorId: root.hederaAccountId, + operatorKey: root.hederaAccountKey, + tokenName, + tokenSymbol, + nft, + decimals, + initialSupply, + tokenMemo: '', + adminKey, + kycKey, + freezeKey, + wipeKey, + supplyKey, + } + }, 1); + const tokenObject = tokenRepository.create({ tokenId, tokenName, diff --git a/guardian-service/src/policy-engine/helpers/utils.ts b/guardian-service/src/policy-engine/helpers/utils.ts index 7710e3e460..68574dc8b2 100644 --- a/guardian-service/src/policy-engine/helpers/utils.ts +++ b/guardian-service/src/policy-engine/helpers/utils.ts @@ -1,9 +1,19 @@ import { Token } from '@entity/token'; import { Topic } from '@entity/topic'; -import { HederaSDKHelper, HederaUtils, VcDocument, VcDocument as HVcDocument, TopicHelper, VpDocument } from '@hedera-modules'; +import { VcDocument, VcDocument as HVcDocument, TopicHelper, VpDocument } from '@hedera-modules'; import * as mathjs from 'mathjs'; import { AnyBlockType, IPolicyDocument } from '@policy-engine/policy-engine.interface'; -import { DidDocumentStatus, DocumentSignature, DocumentStatus, ExternalMessageEvents, Schema, SchemaEntity, TopicType } from '@guardian/interfaces'; +import { + DidDocumentStatus, + DocumentSignature, + DocumentStatus, + ExternalMessageEvents, + IRootConfig, + Schema, + SchemaEntity, + TopicType, + WorkerTaskType +} from '@guardian/interfaces'; import { ExternalEventChannel, IAuthUser } from '@guardian/common'; import { Schema as SchemaCollection } from '@entity/schema'; import { TopicId } from '@hashgraph/sdk'; @@ -11,6 +21,7 @@ import { IPolicyUser, PolicyUser } from '@policy-engine/policy-user'; import { KeyType, Wallet } from '@helpers/wallet'; import { Users } from '@helpers/users'; import { VcDocument as VcDocumentCollection } from '@entity/vc-document'; +import { Workers } from '@helpers/workers'; /** * Data types @@ -20,7 +31,8 @@ export enum DataTypes { REPORT = 'report', MINT = 'mint', RETIREMENT = 'retirement', - USER_ROLE = 'user-role' + USER_ROLE = 'user-role', + MULTI_SIGN = 'MULTI_SIGN' } /** @@ -202,7 +214,7 @@ export class PolicyUtils { ref: AnyBlockType, token: Token, tokenValue: number, - root: any, + root: IRootConfig, targetAccount: string, uuid: string, transactionMemo: string @@ -210,54 +222,22 @@ export class PolicyUtils { const mintId = Date.now(); ref.log(`Mint(${mintId}): Start (Count: ${tokenValue})`); - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey, ref.dryRun); - const tokenId = token.tokenId; - const supplyKey = token.supplyKey; - const adminId = token.adminId; - const adminKey = token.adminKey; - - if (token.tokenType === 'non-fungible') { - const metaData = HederaUtils.decode(uuid); - const data = new Array(Math.floor(tokenValue)); - data.fill(metaData); - const serials: number[] = []; - const dataChunk = PolicyUtils.splitChunk(data, 10); - for (let i = 0; i < dataChunk.length; i++) { - const element = dataChunk[i]; - if (i % 100 === 0) { - ref.log(`Mint(${mintId}): Minting (Chunk: ${i + 1}/${dataChunk.length})`); - } - try { - const newSerials = await client.mintNFT(tokenId, supplyKey, element, transactionMemo); - for (const serial of newSerials) { - serials.push(serial) - } - } catch (error) { - ref.error(`Mint(${mintId}): Mint Error (${error.message})`); - } - } - - ref.log(`Mint(${mintId}): Minted (Count: ${serials.length})`); - ref.log(`Mint(${mintId}): Transfer ${adminId} -> ${targetAccount} `); - - const serialsChunk = PolicyUtils.splitChunk(serials, 10); - for (let i = 0; i < serialsChunk.length; i++) { - const element = serialsChunk[i]; - if (i % 100 === 0) { - ref.log(`Mint(${mintId}): Transfer (Chunk: ${i + 1}/${serialsChunk.length})`); - } - try { - await client.transferNFT(tokenId, targetAccount, adminId, adminKey, element, transactionMemo); - } catch (error) { - ref.error(`Mint(${mintId}): Transfer Error (${error.message})`); - } + const workers = new Workers(); + await workers.addTask({ + type: WorkerTaskType.MINT_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + token, + dryRun: ref.dryRun, + tokenValue, + transactionMemo, + uuid, + targetAccount, mintId } - } else { - await client.mint(tokenId, supplyKey, tokenValue, transactionMemo); - await client.transfer(tokenId, targetAccount, adminId, adminKey, tokenValue, transactionMemo); - } + }, 1); - new ExternalEventChannel().publishMessage(ExternalMessageEvents.TOKEN_MINTED, { tokenId, tokenValue, memo: transactionMemo }); + new ExternalEventChannel().publishMessage(ExternalMessageEvents.TOKEN_MINTED, { tokenId: token.tokenId, tokenValue, memo: transactionMemo }); ref.log(`Mint(${mintId}): End`); } @@ -274,19 +254,23 @@ export class PolicyUtils { ref: AnyBlockType, token: Token, tokenValue: number, - root: any, + root: IRootConfig, targetAccount: string, uuid: string ): Promise { - const tokenId = token.tokenId; - const wipeKey = token.wipeKey; - - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey, ref.dryRun); - if (token.tokenType === 'non-fungible') { - throw Error('unsupported operation'); - } else { - await client.wipe(tokenId, targetAccount, wipeKey, tokenValue, uuid); - } + const workers = new Workers(); + await workers.addTask({ + type: WorkerTaskType.WIPE_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + dryRun: ref.dryRun, + token, + targetAccount, + tokenValue, + uuid + } + }, 1); } /** @@ -431,11 +415,17 @@ export class PolicyUtils { * @param user */ public static async associate(ref: AnyBlockType, token: Token, user: IHederaAccount): Promise { - const client = new HederaSDKHelper(user.hederaAccountId, user.hederaAccountKey, ref.dryRun); - if (!user.hederaAccountKey) { - throw new Error('Invalid Account Key'); - } - return await client.associate(token.tokenId, user.hederaAccountId, user.hederaAccountKey); + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.ASSOCIATE_TOKEN, + data: { + tokenId: token.tokenId, + userID: user.hederaAccountId, + userKey: user.hederaAccountKey, + associate: true, + dryRun: ref.dryRun + } + }, 1); } /** @@ -444,11 +434,17 @@ export class PolicyUtils { * @param user */ public static async dissociate(ref: AnyBlockType, token: Token, user: IHederaAccount): Promise { - const client = new HederaSDKHelper(user.hederaAccountId, user.hederaAccountKey, ref.dryRun); - if (!user.hederaAccountKey) { - throw new Error('Invalid Account Key'); - } - return await client.dissociate(token.tokenId, user.hederaAccountId, user.hederaAccountKey); + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.ASSOCIATE_TOKEN, + data: { + tokenId: token.tokenId, + userID: user.hederaAccountId, + userKey: user.hederaAccountKey, + associate: false, + dryRun: ref.dryRun + } + }, 1); } /** @@ -463,8 +459,18 @@ export class PolicyUtils { user: IHederaAccount, root: IHederaAccount ): Promise { - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey, ref.dryRun); - return await client.freeze(token.tokenId, user.hederaAccountId, token.freezeKey); + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.FREEZE_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + freezeKey: token.freezeKey, + tokenId: token.tokenId, + freeze: true, + dryRun: ref.dryRun + } + }, 1); } /** @@ -479,8 +485,18 @@ export class PolicyUtils { user: IHederaAccount, root: IHederaAccount ): Promise { - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey, ref.dryRun); - return await client.unfreeze(token.tokenId, user.hederaAccountId, token.freezeKey); + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.FREEZE_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + freezeKey: token.freezeKey, + tokenId: token.tokenId, + freeze: false, + dryRun: ref.dryRun + } + }, 1); } /** @@ -495,8 +511,19 @@ export class PolicyUtils { user: IHederaAccount, root: IHederaAccount ): Promise { - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey, ref.dryRun); - return await client.grantKyc(token.tokenId, user.hederaAccountId, token.kycKey); + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.GRANT_KYC_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + userHederaAccountId: user.hederaAccountId, + tokenId: token.tokenId, + kycKey: token.kycKey, + grant: true, + dryRun: ref.dryRun + } + }, 1); } /** @@ -511,18 +538,33 @@ export class PolicyUtils { user: IHederaAccount, root: IHederaAccount ): Promise { - const client = new HederaSDKHelper(root.hederaAccountId, root.hederaAccountKey, ref.dryRun); - return await client.revokeKyc(token.tokenId, user.hederaAccountId, token.kycKey); + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.GRANT_KYC_TOKEN, + data: { + hederaAccountId: root.hederaAccountId, + hederaAccountKey: root.hederaAccountKey, + userHederaAccountId: user.hederaAccountId, + tokenId: token.tokenId, + kycKey: token.kycKey, + grant: false, + dryRun: ref.dryRun + } + }, 1); } /** * revokeKyc * @param account */ - public static checkAccountId(account: IHederaAccount): void { - if (!account || !HederaSDKHelper.checkAccount(account.hederaAccountId)) { - throw new Error('Invalid Account'); - } + public static async checkAccountId(account: IHederaAccount): Promise { + const workers = new Workers(); + return await workers.addTask({ + type: WorkerTaskType.CHECK_ACCOUNT, + data: { + hederaAccountId: account.hederaAccountId, + } + }, 1); } /** @@ -535,7 +577,7 @@ export class PolicyUtils { public static async getOrCreateTopic( ref: AnyBlockType, topicName: string, - root: any, + root: IRootConfig, user: any, memoObj?: any ): Promise { @@ -909,6 +951,7 @@ export class PolicyUtils { owner: owner.did, group: owner.group, assignedTo: null, + assignedToGroup: null, option: null, schema: null, hederaStatus: DocumentStatus.NEW, @@ -936,6 +979,7 @@ export class PolicyUtils { owner: document.owner, group: document.group, assignedTo: document.assignedTo || null, + assignedToGroup: document.assignedToGroup || null, option: document.option || null, schema: document.schema || null, hederaStatus: document.hederaStatus || DocumentStatus.NEW, diff --git a/guardian-service/src/policy-engine/interfaces/block-about.ts b/guardian-service/src/policy-engine/interfaces/block-about.ts index d352e8ee26..b2a577f121 100644 --- a/guardian-service/src/policy-engine/interfaces/block-about.ts +++ b/guardian-service/src/policy-engine/interfaces/block-about.ts @@ -19,6 +19,145 @@ export enum ControlType { None = 'None', } +/** + * Property Type + */ +export enum PropertyType { + Input = 'Input', + Checkbox = 'Checkbox', + Select = 'Select', + MultipleSelect = 'MultipleSelect', + Group = 'Group', + Array = 'Array' +} + +/** + * Block Properties + */ +export interface BlockProperties { + /** + * Property name + */ + name: string; + /** + * Property label + */ + label: string; + /** + * Property title + */ + title: string; + /** + * Property type + */ + type: PropertyType; + /** + * Default value + */ + default?: any; +} + +/** + * Input Properties + */ +export interface InputProperties extends BlockProperties { + /** + * Property type + */ + type: PropertyType.Input; +} + +/** + * Checkbox Properties + */ +export interface CheckboxProperties extends BlockProperties { + /** + * Property type + */ + type: PropertyType.Checkbox; +} + +/** + * Select Properties + */ +export interface SelectProperties extends BlockProperties { + /** + * Property type + */ + type: PropertyType.Select; + /** + * Select data + */ + items: { + /** + * Item label + */ + label: string, + /** + * Item value + */ + value: string + }[] +} + +/** + * MultipleSelect Properties + */ +export interface MultipleSelectProperties extends BlockProperties { + /** + * Property type + */ + type: PropertyType.MultipleSelect; +} + +/** + * Group Properties + */ +export interface GroupProperties extends BlockProperties { + /** + * Property type + */ + type: PropertyType.Group; + /** + * Children + */ + properties: AnyBlockProperties[]; +} + +/** + * Array Properties + */ +export interface ArrayProperties extends BlockProperties { + /** + * Property type + */ + type: PropertyType.Array; + /** + * Array item description + */ + items: { + /** + * Child label + */ + label: string, + /** + * Children + */ + properties: AnyBlockProperties[]; + } +} + +/** + * Any Block Properties + */ +export type AnyBlockProperties = + InputProperties | + CheckboxProperties | + SelectProperties | + MultipleSelectProperties | + GroupProperties | + ArrayProperties; + /** * Block about */ @@ -59,4 +198,8 @@ export interface BlockAbout { * Default event */ defaultEvent: boolean; + /** + * Default properties + */ + properties?: AnyBlockProperties[]; } diff --git a/guardian-service/src/policy-engine/interfaces/policy-event-type.ts b/guardian-service/src/policy-engine/interfaces/policy-event-type.ts index 243967005f..4a73d76309 100644 --- a/guardian-service/src/policy-engine/interfaces/policy-event-type.ts +++ b/guardian-service/src/policy-engine/interfaces/policy-event-type.ts @@ -7,7 +7,8 @@ export enum PolicyInputEventType { StopTimerEvent = 'StopTimerEvent', RefreshEvent = 'RefreshEvent', RunEvent = 'RunEvent', - ReleaseEvent = 'ReleaseEvent' + ReleaseEvent = 'ReleaseEvent', + PopEvent = 'PopEvent' } /** @@ -20,7 +21,9 @@ export enum PolicyOutputEventType { DropdownEvent = 'DropdownEvent', Confirm = 'Confirm', CreateGroup = 'CreateGroup', - JoinGroup = 'JoinGroup' + JoinGroup = 'JoinGroup', + SignatureQuorumReachedEvent= 'SignatureQuorumReachedEvent', + SignatureSetInsufficientEvent= 'SignatureSetInsufficientEvent' } /** diff --git a/guardian-service/src/policy-engine/policy-components-utils.ts b/guardian-service/src/policy-engine/policy-components-utils.ts index 182a857732..467f00d948 100644 --- a/guardian-service/src/policy-engine/policy-components-utils.ts +++ b/guardian-service/src/policy-engine/policy-components-utils.ts @@ -78,6 +78,13 @@ export class PolicyComponentsUtils { */ private static readonly PolicyById: Map = new Map(); + /** + * Policy Internal Events + * policyId -> eventType -> callback + * @private + */ + private static readonly InternalListeners: Map> = new Map(); + /** * Log events * @param text @@ -360,6 +367,7 @@ export class PolicyComponentsUtils { } PolicyComponentsUtils.TagMapByPolicyId.delete(policyId); PolicyComponentsUtils.ActionMapByPolicyId.delete(policyId); + PolicyComponentsUtils.InternalListeners.delete(policyId); } /** @@ -588,4 +596,38 @@ export class PolicyComponentsUtils { return result; } + /** + * Add Internal Event Listener + * @param type + */ + public static AddInternalListener(type: string, policyId: string, callback: Function) { + let policyMap = PolicyComponentsUtils.InternalListeners.get(policyId); + if (!policyMap) { + policyMap = new Map(); + PolicyComponentsUtils.InternalListeners.set(policyId, policyMap); + } + let listeners = policyMap.get(type); + if (!listeners) { + listeners = []; + policyMap.set(type, listeners); + } + listeners.push(callback); + } + + /** + * Trigger Internal Event + * @param type + * @param data + */ + public static async TriggerInternalEvent(type: string, policyId: string, data: any): Promise { + const policyMap = PolicyComponentsUtils.InternalListeners.get(policyId); + if (policyMap) { + const listeners = policyMap.get(type); + if (listeners) { + for (const callback of listeners) { + await callback(data); + } + } + } + } } diff --git a/guardian-service/src/policy-engine/policy-engine.interface.ts b/guardian-service/src/policy-engine/policy-engine.interface.ts index 8db4daf2a5..b5d33080d9 100644 --- a/guardian-service/src/policy-engine/policy-engine.interface.ts +++ b/guardian-service/src/policy-engine/policy-engine.interface.ts @@ -307,7 +307,7 @@ export interface IPolicyBlock { addTargetLink(link: any): void; /** - * Run block acrions + * Run block action * @param event */ runAction(event: IPolicyEvent): Promise; @@ -318,6 +318,29 @@ export interface IPolicyBlock { * @param state */ updateDataState(user: IPolicyUser, state: any): boolean; + + /** + * Join GET Data + * @param {IPolicyDocument | IPolicyDocument[]} data + * @param {IPolicyUser} user + * @param {AnyBlockType} parent + */ + joinData( + data: T, user: IPolicyUser, parent: AnyBlockType + ): Promise; + + /** + * Add Internal Event Listener + * @param type + */ + addInternalListener(type: string, callback: Function): void; + + /** + * Trigger Internal Event + * @param type + * @param data + */ + triggerInternalEvent(type: string, data: any): void; } /** @@ -499,7 +522,7 @@ export interface IPolicyAddonBlock extends IPolicyBlock { * Get filters * @param user */ - getFilters(user: IPolicyUser): { [key: string]: string }; + getFilters(user: IPolicyUser): Promise<{ [key: string]: string }>; /** * Set filters @@ -659,6 +682,10 @@ export interface IPolicyDocument { * Assigned To */ assignedTo?: string; + /** + * Assigned To + */ + assignedToGroup?: string; /** * Message Id */ diff --git a/guardian-service/src/policy-engine/policy-engine.service.ts b/guardian-service/src/policy-engine/policy-engine.service.ts index 2dee21823c..6244a7b501 100644 --- a/guardian-service/src/policy-engine/policy-engine.service.ts +++ b/guardian-service/src/policy-engine/policy-engine.service.ts @@ -1,13 +1,6 @@ import { PolicyEngineEvents, - SchemaEntity, - SchemaStatus, TopicType, - ModelHelper, - SchemaHelper, - Schema, - UserRole, - IUser, PolicyType } from '@guardian/interfaces'; import { @@ -20,47 +13,18 @@ import { } from '@guardian/common'; import { DIDDocument, - HederaSDKHelper, - MessageAction, - MessageServer, - MessageType, - PolicyMessage, - TopicHelper } from '@hedera-modules' -import { findAllEntities, replaceAllEntities, SchemaFields } from '@helpers/utils'; -import { IPolicyBlock, IPolicyInstance, IPolicyInterfaceBlock } from './policy-engine.interface'; -import { incrementSchemaVersion, findAndPublishSchema, publishSystemSchema, findAndDryRunSchema, deleteSchema } from '@api/schema.service'; +import { IPolicyBlock, IPolicyInterfaceBlock } from './policy-engine.interface'; import { PolicyImportExportHelper } from './helpers/policy-import-export-helper'; -import { VcHelper } from '@helpers/vc-helper'; import { Users } from '@helpers/users'; import { Inject } from '@helpers/decorators/inject'; import { Policy } from '@entity/policy'; import { PolicyComponentsUtils } from './policy-components-utils'; -import { BlockTreeGenerator } from './block-tree-generator'; -import { Topic } from '@entity/topic'; -import { PolicyConverterUtils } from './policy-converter-utils'; import { DatabaseServer } from '@database-modules'; -import { IPolicyUser, PolicyUser } from './policy-user'; -import { emptyNotifier, initNotifier, INotifier } from '@helpers/notifier'; -import { ISerializedErrors } from './policy-validation-results-container'; - -/** - * Result of publishing - */ -interface IPublishResult { - /** - * Policy Id - */ - policyId: string; - /** - * Is policy valid - */ - isValid: boolean; - /** - * Errors of validation - */ - errors: ISerializedErrors; -} +import { IPolicyUser } from './policy-user'; +import { emptyNotifier, initNotifier } from '@helpers/notifier'; +import { PolicyEngine } from './policy-engine'; +import { AccountId, PrivateKey } from '@hashgraph/sdk'; /** * Policy engine service @@ -78,33 +42,31 @@ export class PolicyEngineService { * @private */ private readonly channel: MessageBrokerChannel; - /** - * Policy generator - * @private - */ - private readonly policyGenerator: BlockTreeGenerator; /** * API-gateway message broker service */ private readonly apiGatewayChannel: MessageBrokerChannel; + /** + * Policy Engine + * @private + */ + private readonly policyEngine: PolicyEngine; + constructor(channel: MessageBrokerChannel, apiGatewayChannel: MessageBrokerChannel) { this.channel = channel; this.apiGatewayChannel = apiGatewayChannel; - this.policyGenerator = new BlockTreeGenerator(); - - PolicyComponentsUtils.BlockUpdateFn = async (...args: any[]) => { - await this.stateChangeCb.apply(this, args); - }; - - PolicyComponentsUtils.BlockErrorFn = async (...args: any[]) => { - await this.blockErrorCb.apply(this, args); - }; + this.policyEngine = new PolicyEngine() + } - PolicyComponentsUtils.UpdateUserInfoFn = async (...args: any[]) => { - await this.updateUserInfo.apply(this, args); - } + /** + * Get user by username + * @param username + */ + private async getUserDid(username: string): Promise { + const userFull = await this.users.getUser(username); + return userFull?.did; } /** @@ -192,595 +154,22 @@ export class PolicyEngineService { } /** - * Get user - * @param policy - * @param user - * @private - */ - private async getUser(policy: IPolicyInstance, user: IUser): Promise { - const regUser = await this.users.getUser(user.username); - if (!regUser || !regUser.did) { - throw new Error(`Forbidden`); - } - const userFull = new PolicyUser(regUser.did); - if (policy.dryRun) { - if (user.role === UserRole.STANDARD_REGISTRY) { - const virtualUser = await DatabaseServer.getVirtualUser(policy.policyId); - userFull.setVirtualUser(virtualUser); - } else { - throw new Error(`Forbidden`); - } - } - const groups = await policy.databaseServer.getGroupsByUser(policy.policyId, userFull.did); - for (const group of groups) { - if (group.active !== false) { - return userFull.setGroup(group); - } - } - return userFull; - } - - /** - * Create policy - * @param data - * @param owner + * Register endpoints for policy engine * @private */ - private async createPolicy(data: Policy, owner: string, notifier: INotifier): Promise { - const logger = new Logger(); - logger.info('Create Policy', ['GUARDIAN_SERVICE']); - notifier.start('Save in DB'); - if (data) { - delete data.status; - } - const model = DatabaseServer.createPolicy(data); - if (model.uuid) { - const old = await DatabaseServer.getPolicyByUUID(model.uuid); - if (model.creator !== owner) { - throw new Error('Invalid owner'); - } - if (old.creator !== owner) { - throw new Error('Invalid owner'); - } - model.creator = owner; - model.owner = owner; - delete model.version; - delete model.messageId; - } else { - model.creator = owner; - model.owner = owner; - delete model.previousVersion; - delete model.topicId; - delete model.version; - delete model.messageId; - } - - let newTopic: Topic; - notifier.completedAndStart('Resolve Hedera account'); - const root = await this.users.getHederaAccount(owner); - notifier.completed(); - if (!model.topicId) { - notifier.start('Create topic'); - logger.info('Create Policy: Create New Topic', ['GUARDIAN_SERVICE']); - const parent = await DatabaseServer.getTopicByType(owner, TopicType.UserTopic); - const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey); - - let topic = await topicHelper.create({ - type: TopicType.PolicyTopic, - name: model.name || TopicType.PolicyTopic, - description: model.topicDescription || TopicType.PolicyTopic, - owner, - policyId: null, - policyUUID: null - }); - topic = await DatabaseServer.saveTopic(topic); - model.topicId = topic.topicId; - - notifier.completedAndStart('Create policy in Hedera'); - const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey); - const message = new PolicyMessage(MessageType.Policy, MessageAction.CreatePolicy); - message.setDocument(model); - const messageStatus = await messageServer - .setTopicObject(parent) - .sendMessage(message); - - notifier.completedAndStart('Link topic and policy'); - await topicHelper.twoWayLink(topic, parent, messageStatus.getId()); - - notifier.completedAndStart('Publish schemas'); - const systemSchemas = await PolicyImportExportHelper.getSystemSchemas(); - - notifier.info(`Found ${systemSchemas.length} schemas`); - let num: number = 0; - for (const schema of systemSchemas) { - logger.info('Create Policy: Publish System Schema', ['GUARDIAN_SERVICE']); - messageServer.setTopicObject(topic); - schema.creator = owner; - schema.owner = owner; - const item = await publishSystemSchema(schema, messageServer, MessageAction.PublishSystemSchema); - await DatabaseServer.createAndSaveSchema(item); - const name = item.name; - num++; - notifier.info(`Schema ${num} (${name || '-'}) published`); - } - - newTopic = topic; - notifier.completed(); - } - - notifier.start('Saving in DB'); - model.codeVersion = PolicyConverterUtils.VERSION; - const policy = await DatabaseServer.updatePolicy(model); - - if (newTopic) { - newTopic.policyId = policy.id.toString(); - newTopic.policyUUID = policy.uuid; - await DatabaseServer.updateTopic(newTopic); - } - - notifier.completed(); - return policy; - } - - /** - * Clone policy - */ - private async clonePolicy(policyId: string, data: any, owner: string, notifier: INotifier): Promise { - const logger = new Logger(); - logger.info('Create Policy', ['GUARDIAN_SERVICE']); - - const policy = await DatabaseServer.getPolicyById(policyId); - if (!policy) { - throw new Error('Policy does not exists'); - } - if (policy.creator !== owner) { - throw new Error('Invalid owner'); - } - - const schemas = await DatabaseServer.getSchemas({ - topicId: policy.topicId, - readonly: false - }); - - const tokenIds = findAllEntities(policy.config, ['tokenId']); - const tokens = await DatabaseServer.getTokens({ - tokenId: { $in: tokenIds } - }); - - const dataToCreate = { - policy, schemas, tokens + public registerListeners(): void { + PolicyComponentsUtils.BlockUpdateFn = async (...args: any[]) => { + await this.stateChangeCb.apply(this, args); }; - const newPolicy = await PolicyImportExportHelper.importPolicy(dataToCreate, owner, null, notifier, data); - return newPolicy.id; - } - - /** - * Delete policy - * @param policyId Policy ID - * @param user User - * @param notifier Notifier - * @returns Result - */ - private async deletePolicy(policyId: string, user: IAuthUser, notifier: INotifier): Promise { - const logger = new Logger(); - logger.info('Delete Policy', ['GUARDIAN_SERVICE']); - - const policyToDelete = await DatabaseServer.getPolicyById(policyId); - if (policyToDelete.owner !== user.did) { - throw new Error('Insufficient permissions to delete the policy'); - } - - if (policyToDelete.status !== PolicyType.DRAFT) { - throw new Error('Policy is not in draft status'); - } - - notifier.start('Delete schemas'); - const schemasToDelete = await DatabaseServer.getSchemas({ - topicId: policyToDelete.topicId, - readonly: false - }); - for (const schema of schemasToDelete) { - if (schema.status === SchemaStatus.DRAFT) { - await deleteSchema(schema.id, notifier); - } - } - - notifier.completedAndStart('Publishing delete policy message'); - const topic = await DatabaseServer.getTopicById(policyToDelete.topicId); - const users = new Users(); - const root = await users.getHederaAccount(policyToDelete.owner); - const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey); - const message = new PolicyMessage(MessageType.Policy, MessageAction.DeletePolicy); - message.setDocument(policyToDelete); - await messageServer.setTopicObject(topic) - .sendMessage(message); - - notifier.completedAndStart('Delete policy from DB'); - await DatabaseServer.deletePolicy(policyId); - notifier.completed(); - return true; - } - - /** - * Policy schemas - * @param model - * @param owner - * @private - */ - private async publishSchemas(model: Policy, owner: string, notifier: INotifier): Promise { - const schemas = await DatabaseServer.getSchemas({ topicId: model.topicId }); - notifier.info(`Found ${schemas.length} schemas`); - const schemaIRIs = schemas.map(s => s.iri); - let num: number = 0; - let skipped: number = 0; - for (const schemaIRI of schemaIRIs) { - const schema = await incrementSchemaVersion(schemaIRI, owner); - if (!schema || schema.status === SchemaStatus.PUBLISHED) { - skipped++; - continue; - } - const newSchema = await findAndPublishSchema(schema.id, schema.version, owner, emptyNotifier()); - replaceAllEntities(model.config, SchemaFields, schemaIRI, newSchema.iri); - - const name = newSchema.name; - num++; - notifier.info(`Schema ${num} (${name || '-'}) published`); - } - - if (skipped) { - notifier.info(`Skip published ${skipped}`); - } - return model; - } - - /** - * Dry run Policy schemas - * @param model - * @param owner - * @private - */ - private async dryRunSchemas(model: Policy, owner: string): Promise { - const schemas = await DatabaseServer.getSchemas({ topicId: model.topicId }); - for (const schema of schemas) { - if (schema.status === SchemaStatus.PUBLISHED) { - continue; - } - await findAndDryRunSchema(schema, schema.version, owner); - } - return model; - } - - /** - * Publish policy - * @param model - * @param owner - * @param version - * @private - */ - private async publishPolicy(model: Policy, owner: string, version: string, notifier: INotifier): Promise { - const logger = new Logger(); - logger.info('Publish Policy', ['GUARDIAN_SERVICE']); - notifier.start('Resolve Hedera account'); - const root = await this.users.getHederaAccount(owner); - notifier.completedAndStart('Find topic'); - - const topic = await DatabaseServer.getTopicById(model.topicId); - const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey) - .setTopicObject(topic); - - notifier.completedAndStart('Publish schemas'); - model = await this.publishSchemas(model, owner, notifier); - model.status = PolicyType.PUBLISH; - model.version = version; - - notifier.completedAndStart('Generate file'); - this.policyGenerator.regenerateIds(model.config); - const zip = await PolicyImportExportHelper.generateZipFile(model); - const buffer = await zip.generateAsync({ type: 'arraybuffer' }); - - notifier.completedAndStart('Create topic'); - const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey); - let rootTopic = await topicHelper.create({ - type: TopicType.InstancePolicyTopic, - name: model.name || TopicType.InstancePolicyTopic, - description: model.topicDescription || TopicType.InstancePolicyTopic, - owner, - policyId: model.id.toString(), - policyUUID: model.uuid - }); - rootTopic = await DatabaseServer.saveTopic(rootTopic); - - notifier.completedAndStart('Publish policy'); - const message = new PolicyMessage(MessageType.InstancePolicy, MessageAction.PublishPolicy); - message.setDocument(model, buffer); - const result = await messageServer - .sendMessage(message); - model.messageId = result.getId(); - model.instanceTopicId = rootTopic.topicId; - notifier.completedAndStart('Link topic and policy'); - await topicHelper.twoWayLink(rootTopic, topic, result.getId()); - - notifier.completedAndStart('Update policy schema'); - const messageId = result.getId(); - const url = result.getUrl(); - - const policySchema = await DatabaseServer.getSchemaByType(model.topicId, SchemaEntity.POLICY); - - const vcHelper = new VcHelper(); - let credentialSubject: any = { - id: messageId, - name: model.name, - description: model.description, - topicDescription: model.topicDescription, - version: model.version, - policyTag: model.policyTag, - owner: model.owner, - cid: url.cid, - url: url.url, - uuid: model.uuid, - operation: 'PUBLISH' - } - if (policySchema) { - const schemaObject = new Schema(policySchema); - credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); - } - - notifier.completedAndStart('Create VC'); - const vc = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); - await DatabaseServer.saveVC({ - hash: vc.toCredentialHash(), - owner, - document: vc.toJsonTree(), - type: SchemaEntity.POLICY, - policyId: `${model.id}` - }); - - logger.info('Published Policy', ['GUARDIAN_SERVICE']); - notifier.completedAndStart('Saving in DB'); - const retVal = await DatabaseServer.updatePolicy(model); - notifier.completed(); - return retVal - } - - /** - * Dry Run policy - * @param model - * @param owner - * @param version - * @private - */ - private async dryRunPolicy(model: Policy, owner: string, version: string): Promise { - const logger = new Logger(); - logger.info('Dry-run Policy', ['GUARDIAN_SERVICE']); - - const root = await this.users.getHederaAccount(owner); - const topic = await DatabaseServer.getTopicById(model.topicId); - - const dryRunId = model.id.toString(); - const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey, dryRunId) - .setTopicObject(topic); - const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey, dryRunId); - const databaseServer = new DatabaseServer(dryRunId); - - model = await this.dryRunSchemas(model, owner); - model.status = PolicyType.DRY_RUN; - model.version = version; - - this.policyGenerator.regenerateIds(model.config); - const zip = await PolicyImportExportHelper.generateZipFile(model); - const buffer = await zip.generateAsync({ type: 'arraybuffer' }); - - const rootTopic = await topicHelper.create({ - type: TopicType.InstancePolicyTopic, - name: model.name || TopicType.InstancePolicyTopic, - description: model.topicDescription || TopicType.InstancePolicyTopic, - owner, - policyId: model.id.toString(), - policyUUID: model.uuid - }); - databaseServer.saveTopic(rootTopic) - - const message = new PolicyMessage(MessageType.InstancePolicy, MessageAction.PublishPolicy); - message.setDocument(model, buffer); - const result = await messageServer.sendMessage(message); - model.messageId = result.getId(); - model.instanceTopicId = rootTopic.topicId; - - await topicHelper.twoWayLink(rootTopic, topic, result.getId()); - - const messageId = result.getId(); - const url = result.getUrl(); - - const vcHelper = new VcHelper(); - let credentialSubject: any = { - id: messageId, - name: model.name, - description: model.description, - topicDescription: model.topicDescription, - version: model.version, - policyTag: model.policyTag, - owner: model.owner, - cid: url.cid, - url: url.url, - uuid: model.uuid, - operation: 'PUBLISH' - } - - const policySchema = await DatabaseServer.getSchemaByType(model.topicId, SchemaEntity.POLICY); - if (policySchema) { - const schemaObject = new Schema(policySchema); - credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); - } - - const vc = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); - - await databaseServer.saveVC({ - hash: vc.toCredentialHash(), - owner, - document: vc.toJsonTree(), - type: SchemaEntity.POLICY, - policyId: `${model.id}` - }); - - await DatabaseServer.createVirtualUser( - model.id.toString(), - 'Administrator', - root.did, - root.hederaAccountId, - root.hederaAccountKey, - true - ); - - logger.info('Published Policy', ['GUARDIAN_SERVICE']); - - return await DatabaseServer.updatePolicy(model); - } - - /** - * Validate and publish policy - * @param model - * @param policyId - * @param userFull - * @param notifier - */ - private async validateAndPublishPolicy(model: any, policyId: any, userFull: IAuthUser, notifier: INotifier): Promise { - const version = model.policyVersion; - const owner = userFull.did; - - notifier.start('Find and validate policy'); - const policy = await DatabaseServer.getPolicyById(policyId); - if (!policy) { - throw new Error('Unknown policy'); - } - if (!policy.config) { - throw new Error('The policy is empty'); - } - if (policy.status === PolicyType.PUBLISH) { - throw new Error(`Policy already published`); - } - if (!ModelHelper.checkVersionFormat(version)) { - throw new Error('Invalid version format'); - } - if (ModelHelper.versionCompare(version, policy.previousVersion) <= 0) { - throw new Error('Version must be greater than ' + policy.previousVersion); - } - - const countModels = await DatabaseServer.getPolicyCount({ - version, - uuid: policy.uuid - }); - if (countModels > 0) { - throw new Error('Policy with current version already was published'); - } - - const errors = await this.policyGenerator.validate(policyId); - const isValid = !errors.blocks.some(block => !block.isValid); - notifier.completed(); - if (isValid) { - if (policy.status === PolicyType.DRY_RUN) { - await this.policyGenerator.destroy(policy.id.toString()); - await DatabaseServer.clearDryRun(policy.id.toString()); - } - const newPolicy = await this.publishPolicy(policy, owner, version, notifier); - await this.policyGenerator.generate(newPolicy.id.toString()); - return { - policyId: newPolicy.id.toString(), - isValid, - errors - }; - } else { - return { - policyId: policy.id.toString(), - isValid, - errors - }; - } - } - - /** - * Prepare policy for preview by message - * @param messageId - * @param user - * @param notifier - */ - private async preparePolicyPreviewMessage(messageId, user, notifier: INotifier): Promise { - notifier.start('Resolve Hedera account'); - const userFull = await this.users.getUser(user.username); - if (!messageId) { - throw new Error('Policy ID in body is empty'); - } - - new Logger().info(`Import policy by message`, ['GUARDIAN_SERVICE']); - - const root = await this.users.getHederaAccount(userFull.did); - - const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey); - const message = await messageServer.getMessage(messageId); - if (message.type !== MessageType.InstancePolicy) { - throw new Error('Invalid Message Type'); - } - - if (!message.document) { - throw new Error('file in body is empty'); - } - - notifier.completedAndStart('Load policy files'); - const newVersions: any = []; - if (message.version) { - const anotherVersions = await messageServer.getMessages( - message.getTopicId(), MessageType.InstancePolicy, MessageAction.PublishPolicy - ); - for (const element of anotherVersions) { - if (element.version && ModelHelper.versionCompare(element.version, message.version) === 1) { - newVersions.push({ - messageId: element.getId(), - version: element.version - }); - } - }; - } - notifier.completedAndStart('Parse policy files'); - const policyToImport = await PolicyImportExportHelper.parseZipFile(message.document); - if (newVersions.length !== 0) { - policyToImport.newVersions = newVersions.reverse(); - } - - notifier.completed(); - return policyToImport; - } + PolicyComponentsUtils.BlockErrorFn = async (...args: any[]) => { + await this.blockErrorCb.apply(this, args); + }; - /** - * Import policy by message - * @param messageId - * @param userFull - * @param hederaAccount - * @param versionOfTopicId - * @param notifier - */ - private async importPolicyMessage(messageId, userFull: IAuthUser, hederaAccount, versionOfTopicId: string, notifier: INotifier): Promise { - notifier.start('Load from IPFS'); - const messageServer = new MessageServer(hederaAccount.hederaAccountId, hederaAccount.hederaAccountKey); - const message = await messageServer.getMessage(messageId); - if (message.type !== MessageType.InstancePolicy) { - throw new Error('Invalid Message Type'); - } - if (!message.document) { - throw new Error('File in body is empty'); + PolicyComponentsUtils.UpdateUserInfoFn = async (...args: any[]) => { + await this.updateUserInfo.apply(this, args); } - notifier.completedAndStart('File parsing'); - const policyToImport = await PolicyImportExportHelper.parseZipFile(message.document); - notifier.completed(); - const policy = await PolicyImportExportHelper.importPolicy(policyToImport, userFull.did, versionOfTopicId, notifier); - return policy; - } - - /** - * Register endpoints for policy engine - * @private - */ - public registerListeners(): void { this.channel.response('mrv-data', async (msg) => { await PolicyComponentsUtils.ReceiveExternalData(msg); return new MessageResponse({}) @@ -826,9 +215,9 @@ export class PolicyEngineService { this.channel.response(PolicyEngineEvents.CREATE_POLICIES, async (msg) => { try { const user = msg.user; - const userFull = await this.users.getUser(user.username); - await this.createPolicy(msg.model, userFull.did, emptyNotifier()); - const policies = await DatabaseServer.getPolicies({ owner: userFull.did }); + const did = await this.getUserDid(user.username); + await this.policyEngine.createPolicy(msg.model, did, emptyNotifier()); + const policies = await DatabaseServer.getPolicies({ owner: did }); return new MessageResponse(policies); } catch (error) { return new MessageError(error); @@ -840,8 +229,8 @@ export class PolicyEngineService { const notifier = initNotifier(this.apiGatewayChannel, taskId); setImmediate(async () => { try { - const userFull = await this.users.getUser(user.username); - const policy = await this.createPolicy(model, userFull.did, notifier); + const did = await this.getUserDid(user.username); + const policy = await this.policyEngine.createPolicy(model, did, notifier); notifier.result(policy.id); } catch (error) { notifier.error(error); @@ -855,7 +244,7 @@ export class PolicyEngineService { const notifier = initNotifier(this.apiGatewayChannel, taskId); setImmediate(async () => { try { - notifier.result(await this.clonePolicy(policyId, model, user.did, notifier)); + notifier.result(await this.policyEngine.clonePolicy(policyId, model, user.did, notifier)); } catch (error) { notifier.error(error); } @@ -868,7 +257,7 @@ export class PolicyEngineService { const notifier = initNotifier(this.apiGatewayChannel, taskId); setImmediate(async () => { try { - notifier.result(await this.deletePolicy(policyId, user, notifier)); + notifier.result(await this.policyEngine.deletePolicy(policyId, user, notifier)); } catch (error) { notifier.error(error); } @@ -893,11 +282,9 @@ export class PolicyEngineService { } const { model, policyId, user } = msg; - const userFull = await this.users.getUser(user.username); - - const result = await this.validateAndPublishPolicy(model, policyId, userFull, emptyNotifier()); + const owner = await this.getUserDid(user.username); - const owner = userFull.did; + const result = await this.policyEngine.validateAndPublishPolicy(model, policyId, owner, emptyNotifier()); const policies = (await DatabaseServer.getPolicies({ owner })); return new MessageResponse({ @@ -922,9 +309,9 @@ export class PolicyEngineService { } notifier.start('Resolve Hedera account'); - const userFull = await this.users.getUser(user.username); + const owner = await this.getUserDid(user.username); notifier.completed(); - const result = await this.validateAndPublishPolicy(model, policyId, userFull, notifier); + const result = await this.policyEngine.validateAndPublishPolicy(model, policyId, owner, notifier); notifier.result(result); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -937,7 +324,7 @@ export class PolicyEngineService { this.channel.response(PolicyEngineEvents.DRY_RUN_POLICIES, async (msg) => { try { - const policyId = msg.policyId; + const policyId: string = msg.policyId; const user = msg.user; const model = await DatabaseServer.getPolicyById(policyId); @@ -954,15 +341,14 @@ export class PolicyEngineService { throw new Error(`Policy already in Dry Run`); } - const userFull = await this.users.getUser(user.username); - const owner = userFull.did; + const owner = await this.getUserDid(user.username); - const errors = await this.policyGenerator.validate(policyId); + const errors = await this.policyEngine.validateModel(policyId); const isValid = !errors.blocks.some(block => !block.isValid); if (isValid) { - const newPolicy = await this.dryRunPolicy(model, owner, 'Dry Run'); - await this.policyGenerator.generate(newPolicy.id.toString()); + const newPolicy = await this.policyEngine.dryRunPolicy(model, owner, 'Dry Run'); + await this.policyEngine.generateModel(newPolicy.id.toString()); } const policies = (await DatabaseServer.getPolicies({ owner })); @@ -997,15 +383,14 @@ export class PolicyEngineService { throw new Error(`Policy already in draft`); } - const userFull = await this.users.getUser(user.username); - const owner = userFull.did; + const owner = await this.getUserDid(user.username); model.status = PolicyType.DRAFT; model.version = ''; await DatabaseServer.updatePolicy(model); - await this.policyGenerator.destroy(model.id.toString()); + await this.policyEngine.destroyModel(model.id.toString()); const databaseServer = new DatabaseServer(model.id.toString()); await databaseServer.clearDryRun(); @@ -1024,7 +409,7 @@ export class PolicyEngineService { this.channel.response(PolicyEngineEvents.VALIDATE_POLICIES, async (msg) => { try { const policy = msg.model as Policy; - const results = await this.policyGenerator.validate(policy); + const results = await this.policyEngine.validateModel(policy); return new MessageResponse({ results, policy @@ -1040,8 +425,8 @@ export class PolicyEngineService { const { user, policyId } = msg; const policyInstance = PolicyComponentsUtils.GetPolicyInstance(policyId); - const block = this.policyGenerator.getRoot(policyId); - const userFull = await this.getUser(policyInstance, user); + const block = this.policyEngine.getRoot(policyId); + const userFull = await this.policyEngine.getUser(policyInstance, user); if (block && (await block.isAvailable(userFull))) { const data = await block.getData(userFull, block.uuid); @@ -1061,7 +446,7 @@ export class PolicyEngineService { const policyInstance = PolicyComponentsUtils.GetPolicyInstance(policyId); const block = PolicyComponentsUtils.GetBlockByUUID(blockId); - const userFull = await this.getUser(policyInstance, user); + const userFull = await this.policyEngine.getUser(policyInstance, user); if (block && (await block.isAvailable(userFull))) { const data = await block.getData(userFull, blockId, null); @@ -1081,7 +466,7 @@ export class PolicyEngineService { const policyInstance = PolicyComponentsUtils.GetPolicyInstance(policyId); const block = PolicyComponentsUtils.GetBlockByTag(policyId, tag); - const userFull = await this.getUser(policyInstance, user); + const userFull = await this.policyEngine.getUser(policyInstance, user); if (block && (await block.isAvailable(userFull))) { const data = await block.getData(userFull, block.uuid, null); @@ -1101,7 +486,7 @@ export class PolicyEngineService { const policyInstance = PolicyComponentsUtils.GetPolicyInstance(policyId); const block = PolicyComponentsUtils.GetBlockByUUID(blockId); - const userFull = await this.getUser(policyInstance, user); + const userFull = await this.policyEngine.getUser(policyInstance, user); if (block && (await block.isAvailable(userFull))) { const result = await block.setData(userFull, data); @@ -1121,7 +506,7 @@ export class PolicyEngineService { const policyInstance = PolicyComponentsUtils.GetPolicyInstance(policyId); const block = PolicyComponentsUtils.GetBlockByTag(policyId, tag); - const userFull = await this.getUser(policyInstance, user); + const userFull = await this.policyEngine.getUser(policyInstance, user); if (block && (await block.isAvailable(userFull))) { const result = await block.setData(userFull, data); @@ -1172,7 +557,7 @@ export class PolicyEngineService { return new MessageResponse([]); } - const userFull = await this.getUser(policyInstance, user); + const userFull = await this.policyEngine.getUser(policyInstance, user); const groups = await PolicyComponentsUtils.GetGroups(policyInstance, userFull); return new MessageResponse(groups); @@ -1191,7 +576,7 @@ export class PolicyEngineService { throw new Error(`Policy does not contain groups`); } - const userFull = await this.getUser(policyInstance, user); + const userFull = await this.policyEngine.getUser(policyInstance, user); await PolicyComponentsUtils.SelectGroup(policyInstance, userFull, uuid); return new MessageResponse(true); @@ -1262,10 +647,10 @@ export class PolicyEngineService { throw new Error('file in body is empty'); } new Logger().info(`Import policy by file`, ['GUARDIAN_SERVICE']); - const userFull = await this.users.getUser(user.username); + const did = await this.getUserDid(user.username); const policyToImport = await PolicyImportExportHelper.parseZipFile(Buffer.from(zip.data)); - await PolicyImportExportHelper.importPolicy(policyToImport, userFull.did, versionOfTopicId, emptyNotifier()); - const policies = await DatabaseServer.getPolicies({ owner: userFull.did }); + await PolicyImportExportHelper.importPolicy(policyToImport, did, versionOfTopicId, emptyNotifier()); + const policies = await DatabaseServer.getPolicies({ owner: did }); return new MessageResponse(policies); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -1283,11 +668,11 @@ export class PolicyEngineService { throw new Error('file in body is empty'); } new Logger().info(`Import policy by file`, ['GUARDIAN_SERVICE']); - const userFull = await this.users.getUser(user.username); + const did = await this.getUserDid(user.username); notifier.start('File parsing'); const policyToImport = await PolicyImportExportHelper.parseZipFile(Buffer.from(zip.data)); notifier.completed(); - const policy = await PolicyImportExportHelper.importPolicy(policyToImport, userFull.did, versionOfTopicId, notifier); + const policy = await PolicyImportExportHelper.importPolicy(policyToImport, did, versionOfTopicId, notifier); notifier.result(policy.id); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -1301,7 +686,7 @@ export class PolicyEngineService { this.channel.response(PolicyEngineEvents.POLICY_IMPORT_MESSAGE_PREVIEW, async (msg) => { try { const { messageId, user } = msg; - const policyToImport = await this.preparePolicyPreviewMessage(messageId, user, emptyNotifier()); + const policyToImport = await this.policyEngine.preparePolicyPreviewMessage(messageId, user, emptyNotifier()); return new MessageResponse(policyToImport); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -1315,7 +700,7 @@ export class PolicyEngineService { setImmediate(async () => { try { - const policyToImport = await this.preparePolicyPreviewMessage(messageId, user, notifier); + const policyToImport = await this.policyEngine.preparePolicyPreviewMessage(messageId, user, notifier); notifier.result(policyToImport); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -1329,15 +714,15 @@ export class PolicyEngineService { this.channel.response(PolicyEngineEvents.POLICY_IMPORT_MESSAGE, async (msg) => { try { const { messageId, user, versionOfTopicId } = msg; - const userFull = await this.users.getUser(user.username); + const did = await this.getUserDid(user.username); if (!messageId) { throw new Error('Policy ID in body is empty'); } - const root = await this.users.getHederaAccount(userFull.did); + const root = await this.users.getHederaAccount(did); - await this.importPolicyMessage(messageId, userFull, root, versionOfTopicId, emptyNotifier()); - const policies = await DatabaseServer.getPolicies({ owner: userFull.did }); + await this.policyEngine.importPolicyMessage(messageId, did, root, versionOfTopicId, emptyNotifier()); + const policies = await DatabaseServer.getPolicies({ owner: did }); return new MessageResponse(policies); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -1355,10 +740,10 @@ export class PolicyEngineService { throw new Error('Policy ID in body is empty'); } notifier.start('Resolve Hedera account'); - const userFull = await this.users.getUser(user.username); - const root = await this.users.getHederaAccount(userFull.did); + const did = await this.getUserDid(user.username); + const root = await this.users.getHederaAccount(did); notifier.completed(); - const policy = await this.importPolicyMessage(messageId, userFull, root, versionOfTopicId, notifier); + const policy = await this.policyEngine.importPolicyMessage(messageId, did, root, versionOfTopicId, notifier); notifier.result(policy.id); } catch (error) { new Logger().error(error, ['GUARDIAN_SERVICE']); @@ -1420,7 +805,14 @@ export class PolicyEngineService { } const topic = await DatabaseServer.getTopicByType(did, TopicType.UserTopic); - const treasury = await HederaSDKHelper.createVirtualAccount() + + const newPrivateKey = PrivateKey.generate(); + const newAccountId = new AccountId(Date.now()); + const treasury = { + id: newAccountId, + key: newPrivateKey + }; + const didObject = DIDDocument.create(treasury.key, topic.topicId); const userDID = didObject.getDid(); @@ -1474,8 +866,7 @@ export class PolicyEngineService { const policyId = msg.policyId; const user = msg.user; - const userFull = await this.users.getUser(user.username); - const owner = userFull.did; + const owner = await this.getUserDid(user.username); const model = await DatabaseServer.getPolicyById(policyId); if (!model) { @@ -1488,12 +879,12 @@ export class PolicyEngineService { throw new Error(`Policy is not in Dry Run`); } - await this.policyGenerator.destroy(model.id.toString()); + await this.policyEngine.destroyModel(model.id.toString()); const databaseServer = new DatabaseServer(model.id.toString()); await databaseServer.clearDryRun(); - const newPolicy = await this.dryRunPolicy(model, owner, 'Dry Run'); - await this.policyGenerator.generate(newPolicy.id.toString()); + const newPolicy = await this.policyEngine.dryRunPolicy(model, owner, 'Dry Run'); + await this.policyEngine.generateModel(newPolicy.id.toString()); const policies = (await DatabaseServer.getPolicies({ owner })); return new MessageResponse({ diff --git a/guardian-service/src/policy-engine/policy-engine.ts b/guardian-service/src/policy-engine/policy-engine.ts new file mode 100644 index 0000000000..5380aa5237 --- /dev/null +++ b/guardian-service/src/policy-engine/policy-engine.ts @@ -0,0 +1,713 @@ +import { + SchemaEntity, + SchemaStatus, + TopicType, + ModelHelper, + SchemaHelper, + Schema, + UserRole, + IUser, + PolicyType, + IRootConfig +} from '@guardian/interfaces'; +import { + IAuthUser, + Logger +} from '@guardian/common'; +import { + MessageAction, + MessageServer, + MessageType, + PolicyMessage, + TokenMessage, + TopicHelper +} from '@hedera-modules' +import { findAllEntities, replaceAllEntities, SchemaFields } from '@helpers/utils'; +import { IPolicyInstance, IPolicyInterfaceBlock } from './policy-engine.interface'; +import { incrementSchemaVersion, findAndPublishSchema, publishSystemSchema, findAndDryRunSchema, deleteSchema } from '@api/schema.service'; +import { PolicyImportExportHelper } from './helpers/policy-import-export-helper'; +import { VcHelper } from '@helpers/vc-helper'; +import { Users } from '@helpers/users'; +import { Inject } from '@helpers/decorators/inject'; +import { Policy } from '@entity/policy'; +import { BlockTreeGenerator } from './block-tree-generator'; +import { Topic } from '@entity/topic'; +import { PolicyConverterUtils } from './policy-converter-utils'; +import { DatabaseServer } from '@database-modules'; +import { IPolicyUser, PolicyUser } from './policy-user'; +import { emptyNotifier, INotifier } from '@helpers/notifier'; +import { ISerializedErrors } from './policy-validation-results-container'; + +/** + * Result of publishing + */ +interface IPublishResult { + /** + * Policy Id + */ + policyId: string; + /** + * Is policy valid + */ + isValid: boolean; + /** + * Errors of validation + */ + errors: ISerializedErrors; +} + +/** + * Policy engine service + */ +export class PolicyEngine { + /** + * Users helper + * @private + */ + @Inject() + private readonly users: Users; + + /** + * Policy generator + * @private + */ + private readonly policyGenerator: BlockTreeGenerator; + + constructor() { + this.policyGenerator = new BlockTreeGenerator(); + } + + /** + * Get user + * @param policy + * @param user + */ + public async getUser(policy: IPolicyInstance, user: IUser): Promise { + const regUser = await this.users.getUser(user.username); + if (!regUser || !regUser.did) { + throw new Error(`Forbidden`); + } + const userFull = new PolicyUser(regUser.did); + if (policy.dryRun) { + if (user.role === UserRole.STANDARD_REGISTRY) { + const virtualUser = await DatabaseServer.getVirtualUser(policy.policyId); + userFull.setVirtualUser(virtualUser); + } else { + throw new Error(`Forbidden`); + } + } else { + userFull.setUsername(regUser.username); + } + const groups = await policy.databaseServer.getGroupsByUser(policy.policyId, userFull.did); + for (const group of groups) { + if (group.active !== false) { + return userFull.setGroup(group); + } + } + return userFull; + } + + /** + * Create policy + * @param data + * @param owner + */ + public async createPolicy(data: Policy, owner: string, notifier: INotifier): Promise { + const logger = new Logger(); + logger.info('Create Policy', ['GUARDIAN_SERVICE']); + notifier.start('Save in DB'); + if (data) { + delete data.status; + } + const model = DatabaseServer.createPolicy(data); + if (model.uuid) { + const old = await DatabaseServer.getPolicyByUUID(model.uuid); + if (model.creator !== owner) { + throw new Error('Invalid owner'); + } + if (old.creator !== owner) { + throw new Error('Invalid owner'); + } + model.creator = owner; + model.owner = owner; + delete model.version; + delete model.messageId; + } else { + model.creator = owner; + model.owner = owner; + delete model.previousVersion; + delete model.topicId; + delete model.version; + delete model.messageId; + } + + let newTopic: Topic; + notifier.completedAndStart('Resolve Hedera account'); + const root = await this.users.getHederaAccount(owner); + notifier.completed(); + if (!model.topicId) { + notifier.start('Create topic'); + logger.info('Create Policy: Create New Topic', ['GUARDIAN_SERVICE']); + const parent = await DatabaseServer.getTopicByType(owner, TopicType.UserTopic); + const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey); + + let topic = await topicHelper.create({ + type: TopicType.PolicyTopic, + name: model.name || TopicType.PolicyTopic, + description: model.topicDescription || TopicType.PolicyTopic, + owner, + policyId: null, + policyUUID: null + }); + topic = await DatabaseServer.saveTopic(topic); + model.topicId = topic.topicId; + + notifier.completedAndStart('Create policy in Hedera'); + const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey); + const message = new PolicyMessage(MessageType.Policy, MessageAction.CreatePolicy); + message.setDocument(model); + const messageStatus = await messageServer + .setTopicObject(parent) + .sendMessage(message); + + notifier.completedAndStart('Link topic and policy'); + await topicHelper.twoWayLink(topic, parent, messageStatus.getId()); + + notifier.completedAndStart('Publish schemas'); + const systemSchemas = await PolicyImportExportHelper.getSystemSchemas(); + + notifier.info(`Found ${systemSchemas.length} schemas`); + let num: number = 0; + for (const schema of systemSchemas) { + logger.info('Create Policy: Publish System Schema', ['GUARDIAN_SERVICE']); + messageServer.setTopicObject(topic); + schema.creator = owner; + schema.owner = owner; + const item = await publishSystemSchema(schema, messageServer, MessageAction.PublishSystemSchema); + await DatabaseServer.createAndSaveSchema(item); + const name = item.name; + num++; + notifier.info(`Schema ${num} (${name || '-'}) published`); + } + + newTopic = topic; + notifier.completed(); + } + + notifier.start('Saving in DB'); + model.codeVersion = PolicyConverterUtils.VERSION; + const policy = await DatabaseServer.updatePolicy(model); + + if (newTopic) { + newTopic.policyId = policy.id.toString(); + newTopic.policyUUID = policy.uuid; + await DatabaseServer.updateTopic(newTopic); + } + + notifier.completed(); + return policy; + } + + /** + * Clone policy + */ + public async clonePolicy(policyId: string, data: any, owner: string, notifier: INotifier): Promise { + const logger = new Logger(); + logger.info('Create Policy', ['GUARDIAN_SERVICE']); + + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy) { + throw new Error('Policy does not exists'); + } + if (policy.creator !== owner) { + throw new Error('Invalid owner'); + } + + const schemas = await DatabaseServer.getSchemas({ + topicId: policy.topicId, + readonly: false + }); + + const tokenIds = findAllEntities(policy.config, ['tokenId']); + const tokens = await DatabaseServer.getTokens({ + tokenId: { $in: tokenIds } + }); + + const dataToCreate = { + policy, schemas, tokens + }; + const newPolicy = await PolicyImportExportHelper.importPolicy(dataToCreate, owner, null, notifier, data); + return newPolicy.id; + } + + /** + * Delete policy + * @param policyId Policy ID + * @param user User + * @param notifier Notifier + * @returns Result + */ + public async deletePolicy(policyId: string, user: IAuthUser, notifier: INotifier): Promise { + const logger = new Logger(); + logger.info('Delete Policy', ['GUARDIAN_SERVICE']); + + const policyToDelete = await DatabaseServer.getPolicyById(policyId); + if (policyToDelete.owner !== user.did) { + throw new Error('Insufficient permissions to delete the policy'); + } + + if (policyToDelete.status !== PolicyType.DRAFT) { + throw new Error('Policy is not in draft status'); + } + + notifier.start('Delete schemas'); + const schemasToDelete = await DatabaseServer.getSchemas({ + topicId: policyToDelete.topicId, + readonly: false + }); + for (const schema of schemasToDelete) { + if (schema.status === SchemaStatus.DRAFT) { + await deleteSchema(schema.id, notifier); + } + } + + notifier.completedAndStart('Publishing delete policy message'); + const topic = await DatabaseServer.getTopicById(policyToDelete.topicId); + const users = new Users(); + const root = await users.getHederaAccount(policyToDelete.owner); + const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey); + const message = new PolicyMessage(MessageType.Policy, MessageAction.DeletePolicy); + message.setDocument(policyToDelete); + await messageServer.setTopicObject(topic) + .sendMessage(message); + + notifier.completedAndStart('Delete policy from DB'); + await DatabaseServer.deletePolicy(policyId); + notifier.completed(); + return true; + } + + /** + * Policy schemas + * @param model + * @param owner + * @param root + * @param notifier + */ + public async publishSchemas(model: Policy, owner: string, root: IRootConfig, notifier: INotifier): Promise { + const schemas = await DatabaseServer.getSchemas({ topicId: model.topicId }); + notifier.info(`Found ${schemas.length} schemas`); + const schemaIRIs = schemas.map(s => s.iri); + let num: number = 0; + let skipped: number = 0; + for (const schemaIRI of schemaIRIs) { + const schema = await incrementSchemaVersion(schemaIRI, owner); + if (!schema || schema.status === SchemaStatus.PUBLISHED) { + skipped++; + continue; + } + const newSchema = await findAndPublishSchema( + schema.id, + schema.version, + owner, + root, + emptyNotifier() + ); + replaceAllEntities(model.config, SchemaFields, schemaIRI, newSchema.iri); + + const name = newSchema.name; + num++; + notifier.info(`Schema ${num} (${name || '-'}) published`); + } + + if (skipped) { + notifier.info(`Skip published ${skipped}`); + } + return model; + } + + /** + * Dry run Policy schemas + * @param model + * @param owner + */ + public async dryRunSchemas(model: Policy, owner: string): Promise { + const schemas = await DatabaseServer.getSchemas({ topicId: model.topicId }); + for (const schema of schemas) { + if (schema.status === SchemaStatus.PUBLISHED) { + continue; + } + await findAndDryRunSchema(schema, schema.version, owner); + } + return model; + } + + /** + * Publish policy + * @param model + * @param owner + * @param version + */ + public async publishPolicy(model: Policy, owner: string, version: string, notifier: INotifier): Promise { + const logger = new Logger(); + logger.info('Publish Policy', ['GUARDIAN_SERVICE']); + notifier.start('Resolve Hedera account'); + const root = await this.users.getHederaAccount(owner); + notifier.completedAndStart('Find topic'); + + const topic = await DatabaseServer.getTopicById(model.topicId); + const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey) + .setTopicObject(topic); + + notifier.completedAndStart('Publish schemas'); + model = await this.publishSchemas(model, owner, root, notifier); + model.status = PolicyType.PUBLISH; + model.version = version; + + notifier.completedAndStart('Generate file'); + this.policyGenerator.regenerateIds(model.config); + const zip = await PolicyImportExportHelper.generateZipFile(model); + const buffer = await zip.generateAsync({ type: 'arraybuffer' }); + + notifier.completedAndStart('Token'); + const tokenIds = findAllEntities(model.config, ['tokenId']); + const tokens = await DatabaseServer.getTokens({ tokenId: { $in: tokenIds }, owner: model.owner }); + for (const token of tokens) { + const tokenMessage = new TokenMessage(MessageAction.UseToken); + tokenMessage.setDocument(token); + await messageServer + .sendMessage(tokenMessage); + } + + notifier.completedAndStart('Create topic'); + const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey); + let rootTopic = await topicHelper.create({ + type: TopicType.InstancePolicyTopic, + name: model.name || TopicType.InstancePolicyTopic, + description: model.topicDescription || TopicType.InstancePolicyTopic, + owner, + policyId: model.id.toString(), + policyUUID: model.uuid + }); + rootTopic = await DatabaseServer.saveTopic(rootTopic); + + notifier.completedAndStart('Publish policy'); + const message = new PolicyMessage(MessageType.InstancePolicy, MessageAction.PublishPolicy); + message.setDocument(model, buffer); + const result = await messageServer + .sendMessage(message); + model.messageId = result.getId(); + model.instanceTopicId = rootTopic.topicId; + + notifier.completedAndStart('Link topic and policy'); + await topicHelper.twoWayLink(rootTopic, topic, result.getId()); + + notifier.completedAndStart('Create VC'); + const messageId = result.getId(); + const url = result.getUrl(); + const policySchema = await DatabaseServer.getSchemaByType(model.topicId, SchemaEntity.POLICY); + const vcHelper = new VcHelper(); + let credentialSubject: any = { + id: messageId, + name: model.name, + description: model.description, + topicDescription: model.topicDescription, + version: model.version, + policyTag: model.policyTag, + owner: model.owner, + cid: url.cid, + url: url.url, + uuid: model.uuid, + operation: 'PUBLISH' + } + if (policySchema) { + const schemaObject = new Schema(policySchema); + credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); + } + const vc = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); + await DatabaseServer.saveVC({ + hash: vc.toCredentialHash(), + owner, + document: vc.toJsonTree(), + type: SchemaEntity.POLICY, + policyId: `${model.id}` + }); + + logger.info('Published Policy', ['GUARDIAN_SERVICE']); + + notifier.completedAndStart('Saving in DB'); + const retVal = await DatabaseServer.updatePolicy(model); + notifier.completed(); + return retVal + } + + /** + * Dry Run policy + * @param model + * @param owner + * @param version + */ + public async dryRunPolicy(model: Policy, owner: string, version: string): Promise { + const logger = new Logger(); + logger.info('Dry-run Policy', ['GUARDIAN_SERVICE']); + + const root = await this.users.getHederaAccount(owner); + const topic = await DatabaseServer.getTopicById(model.topicId); + + const dryRunId = model.id.toString(); + const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey, dryRunId) + .setTopicObject(topic); + const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey, dryRunId); + const databaseServer = new DatabaseServer(dryRunId); + + model = await this.dryRunSchemas(model, owner); + model.status = PolicyType.DRY_RUN; + model.version = version; + + this.policyGenerator.regenerateIds(model.config); + const zip = await PolicyImportExportHelper.generateZipFile(model); + const buffer = await zip.generateAsync({ type: 'arraybuffer' }); + + const rootTopic = await topicHelper.create({ + type: TopicType.InstancePolicyTopic, + name: model.name || TopicType.InstancePolicyTopic, + description: model.topicDescription || TopicType.InstancePolicyTopic, + owner, + policyId: model.id.toString(), + policyUUID: model.uuid + }); + databaseServer.saveTopic(rootTopic) + + const message = new PolicyMessage(MessageType.InstancePolicy, MessageAction.PublishPolicy); + message.setDocument(model, buffer); + const result = await messageServer.sendMessage(message); + model.messageId = result.getId(); + model.instanceTopicId = rootTopic.topicId; + + await topicHelper.twoWayLink(rootTopic, topic, result.getId()); + + const messageId = result.getId(); + const url = result.getUrl(); + + const vcHelper = new VcHelper(); + let credentialSubject: any = { + id: messageId, + name: model.name, + description: model.description, + topicDescription: model.topicDescription, + version: model.version, + policyTag: model.policyTag, + owner: model.owner, + cid: url.cid, + url: url.url, + uuid: model.uuid, + operation: 'PUBLISH' + } + + const policySchema = await DatabaseServer.getSchemaByType(model.topicId, SchemaEntity.POLICY); + if (policySchema) { + const schemaObject = new Schema(policySchema); + credentialSubject = SchemaHelper.updateObjectContext(schemaObject, credentialSubject); + } + + const vc = await vcHelper.createVC(owner, root.hederaAccountKey, credentialSubject); + + await databaseServer.saveVC({ + hash: vc.toCredentialHash(), + owner, + document: vc.toJsonTree(), + type: SchemaEntity.POLICY, + policyId: `${model.id}` + }); + + await DatabaseServer.createVirtualUser( + model.id.toString(), + 'Administrator', + root.did, + root.hederaAccountId, + root.hederaAccountKey, + true + ); + + logger.info('Published Policy', ['GUARDIAN_SERVICE']); + + return await DatabaseServer.updatePolicy(model); + } + + /** + * Validate and publish policy + * @param model + * @param policyId + * @param owner + * @param notifier + */ + public async validateAndPublishPolicy(model: any, policyId: any, owner: string, notifier: INotifier): Promise { + const version = model.policyVersion; + + notifier.start('Find and validate policy'); + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy) { + throw new Error('Unknown policy'); + } + if (!policy.config) { + throw new Error('The policy is empty'); + } + if (policy.status === PolicyType.PUBLISH) { + throw new Error(`Policy already published`); + } + if (!ModelHelper.checkVersionFormat(version)) { + throw new Error('Invalid version format'); + } + if (ModelHelper.versionCompare(version, policy.previousVersion) <= 0) { + throw new Error('Version must be greater than ' + policy.previousVersion); + } + + const countModels = await DatabaseServer.getPolicyCount({ + version, + uuid: policy.uuid + }); + if (countModels > 0) { + throw new Error('Policy with current version already was published'); + } + + const errors = await this.policyGenerator.validate(policyId); + const isValid = !errors.blocks.some(block => !block.isValid); + notifier.completed(); + if (isValid) { + if (policy.status === PolicyType.DRY_RUN) { + await this.policyGenerator.destroy(policy.id.toString()); + await DatabaseServer.clearDryRun(policy.id.toString()); + } + const newPolicy = await this.publishPolicy(policy, owner, version, notifier); + await this.policyGenerator.generate(newPolicy.id.toString()); + return { + policyId: newPolicy.id.toString(), + isValid, + errors + }; + } else { + return { + policyId: policy.id.toString(), + isValid, + errors + }; + } + } + + /** + * Prepare policy for preview by message + * @param messageId + * @param user + * @param notifier + */ + public async preparePolicyPreviewMessage(messageId: string, user: any, notifier: INotifier): Promise { + notifier.start('Resolve Hedera account'); + const userFull = await this.users.getUser(user.username); + if (!messageId) { + throw new Error('Policy ID in body is empty'); + } + + new Logger().info(`Import policy by message`, ['GUARDIAN_SERVICE']); + + const root = await this.users.getHederaAccount(userFull.did); + + const messageServer = new MessageServer(root.hederaAccountId, root.hederaAccountKey); + const message = await messageServer.getMessage(messageId); + if (message.type !== MessageType.InstancePolicy) { + throw new Error('Invalid Message Type'); + } + + if (!message.document) { + throw new Error('file in body is empty'); + } + + notifier.completedAndStart('Load policy files'); + const newVersions: any = []; + if (message.version) { + const anotherVersions = await messageServer.getMessages( + message.getTopicId(), MessageType.InstancePolicy, MessageAction.PublishPolicy + ); + for (const element of anotherVersions) { + if (element.version && ModelHelper.versionCompare(element.version, message.version) === 1) { + newVersions.push({ + messageId: element.getId(), + version: element.version + }); + } + }; + } + + notifier.completedAndStart('Parse policy files'); + const policyToImport = await PolicyImportExportHelper.parseZipFile(message.document); + if (newVersions.length !== 0) { + policyToImport.newVersions = newVersions.reverse(); + } + + notifier.completed(); + return policyToImport; + } + + /** + * Import policy by message + * @param messageId + * @param userFull + * @param hederaAccount + * @param versionOfTopicId + * @param notifier + */ + public async importPolicyMessage( + messageId: string, + owner: string, + hederaAccount: IRootConfig, + versionOfTopicId: string, + notifier: INotifier + ): Promise { + notifier.start('Load from IPFS'); + const messageServer = new MessageServer(hederaAccount.hederaAccountId, hederaAccount.hederaAccountKey); + const message = await messageServer.getMessage(messageId); + if (message.type !== MessageType.InstancePolicy) { + throw new Error('Invalid Message Type'); + } + if (!message.document) { + throw new Error('File in body is empty'); + } + + notifier.completedAndStart('File parsing'); + const policyToImport = await PolicyImportExportHelper.parseZipFile(message.document); + notifier.completed(); + const policy = await PolicyImportExportHelper.importPolicy(policyToImport, owner, versionOfTopicId, notifier); + return policy; + } + + /** + * Destroy Model + * @param policyId + */ + public async destroyModel(policyId: string): Promise { + await this.policyGenerator.destroy(policyId); + } + + /** + * Generate Model + * @param policyId + */ + public async generateModel(policyId: string): Promise { + await this.policyGenerator.generate(policyId); + } + + /** + * Validate Model + * @param policy + */ + public async validateModel(policy: Policy | string): Promise { + return await this.policyGenerator.validate(policy); + } + + /** + * Get Root block + * @param policyId + */ + public getRoot(policyId: string): IPolicyInterfaceBlock { + return this.policyGenerator.getRoot(policyId);; + } +} diff --git a/guardian-service/src/policy-engine/policy-user.ts b/guardian-service/src/policy-engine/policy-user.ts index ce904a09e6..5fa600c416 100644 --- a/guardian-service/src/policy-engine/policy-user.ts +++ b/guardian-service/src/policy-engine/policy-user.ts @@ -25,6 +25,10 @@ export interface IPolicyUser { * User DID */ readonly virtual?: boolean; + /** + * username + */ + readonly username?: string; } /** @@ -51,6 +55,10 @@ export class PolicyUser implements IPolicyUser { * User DID */ public virtual?: boolean; + /** + * username + */ + public username?: string; constructor(did: string, virtual: boolean = false) { this.id = did; @@ -94,6 +102,7 @@ export class PolicyUser implements IPolicyUser { if (user) { this.did = user.did; this.virtual = true; + this.username = user.username; } return this; } @@ -107,4 +116,13 @@ export class PolicyUser implements IPolicyUser { const user = new PolicyUser(group.did, virtual); return user.setGroup({ role: group.role, uuid: group.uuid }); } + + /** + * Set username + * @param username + */ + public setUsername(username: string): PolicyUser { + this.username = username; + return this; + } } \ No newline at end of file diff --git a/guardian-service/tests/unit-tests/hedera-modules/vcjs/vcjs.test.js b/guardian-service/tests/unit-tests/hedera-modules/vcjs/vcjs.test.js index 727a85adb4..3782396ab8 100644 --- a/guardian-service/tests/unit-tests/hedera-modules/vcjs/vcjs.test.js +++ b/guardian-service/tests/unit-tests/hedera-modules/vcjs/vcjs.test.js @@ -541,8 +541,7 @@ describe('VCJS', function () { const testVc = await vcjs.createVC( createdDidDocument.getDid(), createdDidDocument.getPrivateKey(), - vcValueToCreate, - schema + vcValueToCreate ) assert.exists(testVc); assert.isTrue(await vcjs.verifyVC(actualVcDocument.document)); diff --git a/interfaces/package.json b/interfaces/package.json index d565e144f4..87c87139c7 100644 --- a/interfaces/package.json +++ b/interfaces/package.json @@ -24,5 +24,6 @@ "lint": "tslint --config ../tslint.json --project .", "test": "echo \"Error: no test specified\" && exit 1" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/interfaces/src/helpers/field-types-dictionary.ts b/interfaces/src/helpers/field-types-dictionary.ts index 286bdf4dfc..73f9260448 100644 --- a/interfaces/src/helpers/field-types-dictionary.ts +++ b/interfaces/src/helpers/field-types-dictionary.ts @@ -82,6 +82,14 @@ export class FieldTypesDictionary { format: undefined, pattern: '^((https):\/\/)?ipfs.io\/ipfs\/.+', isRef: false + }, + { + name: 'Enum', + type: 'string', + format: undefined, + pattern: undefined, + isRef: false, + customType: 'enum' } ]; diff --git a/interfaces/src/helpers/schema-helper.ts b/interfaces/src/helpers/schema-helper.ts index 2d8ab0f1d6..bb807b15b7 100644 --- a/interfaces/src/helpers/schema-helper.ts +++ b/interfaces/src/helpers/schema-helper.ts @@ -14,7 +14,7 @@ export class SchemaHelper { * @param required * @param url */ - public static parseField(name: string, property: any, required: boolean, url: string): SchemaField { + public static parseField(name: string, property: any, required: boolean, url: string): [SchemaField, number] { const field: SchemaField = { name: null, title: null, @@ -34,6 +34,7 @@ export class SchemaHelper { customType: null, }; let _property = property; + const readonly = _property.readOnly; if (_property.oneOf && _property.oneOf.length) { _property = _property.oneOf[0]; } @@ -41,11 +42,16 @@ export class SchemaHelper { field.title = _property.title || name; field.description = _property.description || name; field.isArray = _property.type === SchemaDataTypes.array; + const { + unit, + unitSystem, + customType, + orderPosition + } = SchemaHelper.parseFieldComment(_property.$comment); if (field.isArray) { _property = _property.items; } - field.isRef = !!_property.$ref; - + field.isRef = !!(_property.$ref && !_property.type); if (field.isRef) { field.type = _property.$ref; const { type } = SchemaHelper.parseRef(field.type); @@ -54,17 +60,18 @@ export class SchemaHelper { context: [url] }; } else { - const { unit, unitSystem, customType } = SchemaHelper.parseFieldComment(_property.$comment); field.type = _property.type ? String(_property.type) : null; field.format = _property.format ? String(_property.format) : null; field.pattern = _property.pattern ? String(_property.pattern) : null; field.unit = unit ? String(unit) : null; field.unitSystem = unitSystem ? String(unitSystem) : null; field.customType = customType ? String(customType) : null; + field.enum = _property.enum; + field.remoteLink = _property.$ref; } - field.readOnly = !!_property.readOnly; + field.readOnly = !!(_property.readOnly || readonly); field.required = required; - return field; + return [field, orderPosition]; } /** @@ -73,7 +80,7 @@ export class SchemaHelper { * @param name * @param contextURL */ - public static buildField(field: SchemaField, name: string, contextURL: string): any { + public static buildField(field: SchemaField, name: string, contextURL: string, orderPosition?: number): any { let item: any; const property: any = {}; @@ -93,7 +100,12 @@ export class SchemaHelper { item.$ref = field.type; } else { item.type = field.type; - + if (field.remoteLink) { + item.$ref = field.remoteLink; + } + if (field.enum) { + item.enum = field.enum; + } if (field.format) { item.format = field.format; } @@ -102,7 +114,7 @@ export class SchemaHelper { } } - property.$comment = SchemaHelper.buildFieldComment(field, name, contextURL); + property.$comment = SchemaHelper.buildFieldComment(field, name, contextURL, orderPosition); return property; } @@ -224,13 +236,14 @@ export class SchemaHelper { } } + const fieldsWithPositions = []; const properties = Object.keys(document.properties); for (const name of properties) { const property = document.properties[name]; if (!includeSystemProperties && property.readOnly) { continue; } - const field = SchemaHelper.parseField(name, property, !!required[name], contextURL); + const [field, orderPosition] = SchemaHelper.parseField(name, property, !!required[name], contextURL); if (field.isRef) { const subSchemas = defs || document.$defs; const subDocument = subSchemas[field.type]; @@ -239,10 +252,19 @@ export class SchemaHelper { field.fields = subFields; field.conditions = conditions; } - fields.push(field); + if (orderPosition) { + fieldsWithPositions.push({field, orderPosition}); + } else { + fields.push(field); + } + } - return fields; + return fields.concat( + fieldsWithPositions + .sort((a,b) => a.orderPosition - b.orderPosition) + .map(item => item.field) + ); } /** @@ -297,17 +319,13 @@ export class SchemaHelper { allOf: [] }; - const properties = document.properties; - const required = document.required; - - SchemaHelper.getFieldsFromObject(fields, required, properties, schema.contextURL, false); - if (conditions.length === 0) { delete document.allOf; } const documentConditions = document.allOf; for (const element of conditions) { + const insertingPosition = fields.indexOf(fields.find(item => element.ifCondition.field.name === item.name)) + 1; const ifCondition = {}; ifCondition[element.ifCondition.field.name] = { 'const': element.ifCondition.fieldValue }; const condition = { @@ -321,14 +339,13 @@ export class SchemaHelper { let req = [] let props = {} - SchemaHelper.getFieldsFromObject(element.thenFields, req, props, schema.contextURL, true); - fields.push(...element.thenFields); + SchemaHelper.getFieldsFromObject(element.thenFields, req, props, schema.contextURL); + fields.splice(insertingPosition, 0, ...element.thenFields); if (Object.keys(props).length > 0) { condition.then = { 'properties': props, 'required': req } - document.properties = { ...document.properties, ...props }; } else { delete condition.then; @@ -337,14 +354,13 @@ export class SchemaHelper { req = [] props = {} - SchemaHelper.getFieldsFromObject(element.elseFields, req, props, schema.contextURL, true); - fields.push(...element.elseFields); + SchemaHelper.getFieldsFromObject(element.elseFields, req, props, schema.contextURL); + fields.splice(insertingPosition + element.thenFields.length, 0, ...element.elseFields); if (Object.keys(props).length > 0) { condition.else = { 'properties': props, 'required': req } - document.properties = { ...document.properties, ...props }; } else { delete condition.else; @@ -353,6 +369,8 @@ export class SchemaHelper { documentConditions.push(condition); } + SchemaHelper.getFieldsFromObject(fields, document.required, document.properties, schema.contextURL); + return document; } @@ -365,9 +383,10 @@ export class SchemaHelper { * @param condition * @private */ - private static getFieldsFromObject(fields: SchemaField[], required: string[], properties: any, contextURL: string, condition = false) { + private static getFieldsFromObject(fields: SchemaField[], required: string[], properties: any, contextURL: string) { + const fieldsWithoutSystemFields = fields.filter(item => !item.readOnly); for (const field of fields) { - const property = SchemaHelper.buildField(field, field.name, contextURL); + const property = SchemaHelper.buildField(field, field.name, contextURL, fieldsWithoutSystemFields.indexOf(field)); if (/\s/.test(field.name)) { throw new Error(`Field key '${field.name}' must not contain spaces`); } @@ -746,7 +765,7 @@ export class SchemaHelper { * @param name * @param url */ - public static buildFieldComment(field: SchemaField, name: string, url: string): string { + public static buildFieldComment(field: SchemaField, name: string, url: string, orderPosition?: number): string { const comment: any = {}; comment.term = name; comment['@id'] = field.isRef ? @@ -761,6 +780,9 @@ export class SchemaHelper { if (field.customType) { comment.customType = field.customType; } + if (Number.isInteger(orderPosition) && orderPosition >= 0) { + comment.orderPosition = orderPosition; + } return JSON.stringify(comment); } diff --git a/interfaces/src/interface/messages/auth.message.ts b/interfaces/src/interface/messages/auth.message.ts index 4cb56f0737..b3c5615806 100644 --- a/interfaces/src/interface/messages/auth.message.ts +++ b/interfaces/src/interface/messages/auth.message.ts @@ -35,3 +35,28 @@ export interface IGetKeyResponse { */ key: string; } + +/** + * Set global application key interface + */ +export interface ISetGlobalApplicationKey { + /** + * Key type + */ + type: string; + + /** + * Key value + */ + key: string +} + +/** + * Set global application key interface + */ +export interface IGetGlobalApplicationKey { + /** + * Key + */ + type: string; +} diff --git a/interfaces/src/interface/messages/socket.message.ts b/interfaces/src/interface/messages/socket.message.ts index 6cd62709ec..8e7fde5dc2 100644 --- a/interfaces/src/interface/messages/socket.message.ts +++ b/interfaces/src/interface/messages/socket.message.ts @@ -65,7 +65,7 @@ export interface IUpdateUserBalanceMessage { /** * User for update */ - user: { + user?: { /** * Username */ @@ -75,4 +75,8 @@ export interface IUpdateUserBalanceMessage { */ did: string }; + /** + * Operator account ID + */ + operatorAccountId?: string; } diff --git a/interfaces/src/interface/root-config.interface.ts b/interfaces/src/interface/root-config.interface.ts index 4f5143592f..ecffc709e4 100644 --- a/interfaces/src/interface/root-config.interface.ts +++ b/interfaces/src/interface/root-config.interface.ts @@ -5,7 +5,7 @@ export interface IRootConfig { /** * ID */ - id: string; + id?: string; /** * Hedera account id */ diff --git a/interfaces/src/interface/schema-field.interface.ts b/interfaces/src/interface/schema-field.interface.ts index 015a30bf50..1823f6e4d6 100644 --- a/interfaces/src/interface/schema-field.interface.ts +++ b/interfaces/src/interface/schema-field.interface.ts @@ -81,4 +81,14 @@ export interface SchemaField { * Full field path */ path?: string; + + /** + * Remote link + */ + remoteLink?: string; + + /** + * Enum values + */ + enum?: string[]; } diff --git a/interfaces/src/interface/vc-document.interface.ts b/interfaces/src/interface/vc-document.interface.ts index 22ae270963..3130ef49f2 100644 --- a/interfaces/src/interface/vc-document.interface.ts +++ b/interfaces/src/interface/vc-document.interface.ts @@ -1,5 +1,5 @@ -import {DocumentStatus} from '../type/document-status.type'; -import {IVC} from './vc.interface'; +import { DocumentStatus } from '../type/document-status.type'; +import { IVC } from './vc.interface'; /** * VC document interface @@ -17,6 +17,10 @@ export interface IVCDocument { * Assign */ assignedTo?: string; + /** + * Assign + */ + assignedToGroup?: string; /** * Hash */ diff --git a/interfaces/src/interface/wallet-account.interface.ts b/interfaces/src/interface/wallet-account.interface.ts index a2de1c923a..9a471b6e59 100644 --- a/interfaces/src/interface/wallet-account.interface.ts +++ b/interfaces/src/interface/wallet-account.interface.ts @@ -2,10 +2,6 @@ * Wallet account interface */ export interface IWalletAccount { - /** - * ID - */ - id: string; /** * Token */ diff --git a/interfaces/src/type/wallet-events.ts b/interfaces/src/type/wallet-events.ts index 63330e8525..d4ea9ef7fc 100644 --- a/interfaces/src/type/wallet-events.ts +++ b/interfaces/src/type/wallet-events.ts @@ -3,5 +3,7 @@ */ export enum WalletEvents { GET_KEY = 'get-key', - SET_KEY = 'set-key' + SET_KEY = 'set-key', + SET_GLOBAL_APPLICATION_KEY = 'set-setting-key', + GET_GLOBAL_APPLICATION_KEY = 'get-setting-key' } diff --git a/interfaces/src/type/workers.type.ts b/interfaces/src/type/workers.type.ts index 4f83893d2d..8bcf0151ef 100644 --- a/interfaces/src/type/workers.type.ts +++ b/interfaces/src/type/workers.type.ts @@ -4,7 +4,21 @@ export enum WorkerTaskType { GET_FILE = 'get-file', ADD_FILE = 'add-file', - SEND_HEDERA = 'send-hedera' + SEND_HEDERA = 'send-hedera', + GENERATE_DEMO_KEY = 'generate-demo-key', + GET_USER_BALANCE = 'get-user-balance', + GET_ACCOUNT_INFO = 'get-account-info', + CREATE_TOKEN = 'create-token', + NEW_TOKEN = 'new-token', + ASSOCIATE_TOKEN = 'associate-token', + GRANT_KYC_TOKEN = 'grant-kyc-token', + FREEZE_TOKEN = 'freeze-token', + MINT_TOKEN = 'mint-token', + WIPE_TOKEN = 'wipe-token', + NEW_TOPIC = 'new-topic', + CHECK_ACCOUNT = 'check-account', + GET_TOPIC_MESSAGE = 'get-topic-message', + GET_TOPIC_MESSAGES = 'get-topic-messages' } /** @@ -79,9 +93,13 @@ export interface IActiveTask { * Task */ task: ITask; + /** + * Number of repetitions + */ + number: number; /** * Ready callback * @param data */ - callback: (data: any, error: any) => void; + callback: (data: any, error: any) => void; } diff --git a/ipfs-client/.env b/ipfs-client/.env index 8bcac8fa20..0ef7f5d13d 100644 --- a/ipfs-client/.env +++ b/ipfs-client/.env @@ -1,5 +1,3 @@ MQ_ADDRESS="localhost" SERVICE_CHANNEL="ipfs-client" -DB_HOST="localhost" -DB_DATABASE="ipfs_client_db" -IPFS_STORAGE_API_KEY="..." \ No newline at end of file +IPFS_STORAGE_API_KEY="..." diff --git a/ipfs-client/package.json b/ipfs-client/package.json index 07444f934a..9d85e8ecb0 100644 --- a/ipfs-client/package.json +++ b/ipfs-client/package.json @@ -4,10 +4,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", - "@guardian/interfaces": "^2.4.2", - "@mikro-orm/core": "^5.3.0", - "@mikro-orm/mongodb": "^5.3.0", + "@guardian/common": "^2.5.0-prerelease", + "@guardian/interfaces": "^2.5.0-prerelease", "@web-std/fetch": "3.0.0", "axios": "^0.26.1", "axios-retry": "^3.2.4", @@ -46,5 +44,6 @@ "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ipfs-client.xml", "watch": "nodemon src/index.ts" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/ipfs-client/src/api/file.service.ts b/ipfs-client/src/api/file.service.ts index 5a43549b21..9c42008db7 100644 --- a/ipfs-client/src/api/file.service.ts +++ b/ipfs-client/src/api/file.service.ts @@ -2,7 +2,6 @@ import { Web3Storage } from 'web3.storage'; import Blob from 'cross-blob'; import axios from 'axios'; import axiosRetry from 'axios-retry'; -import { Settings } from '../entity/settings'; import { MessageAPI, ExternalMessageEvents, @@ -13,7 +12,13 @@ import { IFileResponse, GenerateUUIDv4 } from '@guardian/interfaces'; -import { MessageBrokerChannel, MessageError, MessageResponse, Logger, DataBaseHelper } from '@guardian/common'; +import { + MessageBrokerChannel, + MessageError, + MessageResponse, + Logger, + SettingsContainer +} from '@guardian/common'; /** * Public gateway @@ -29,7 +34,6 @@ export const IPFS_PUBLIC_GATEWAY = 'https://ipfs.io/ipfs'; export async function fileAPI( channel: MessageBrokerChannel, client: Web3Storage, - settingsRepository: DataBaseHelper ): Promise { /** * Add file and return hash @@ -194,14 +198,11 @@ export async function fileAPI( */ channel.response(MessageAPI.UPDATE_SETTINGS, async (settings) => { try { - const ipfsStorageApiKey = { - name: 'IPFS_STORAGE_API_KEY', - value: settings.nftApiKey || settings.ipfsStorageApiKey - }; - await settingsRepository.save(ipfsStorageApiKey, { - name: 'IPFS_STORAGE_API_KEY' - }); - client = new Web3Storage({ token: settings.nftApiKey || settings.ipfsStorageApiKey } as any); + const settingsContainer = new SettingsContainer(); + await settingsContainer.updateSetting('IPFS_STORAGE_API_KEY', settings.nftApiKey || settings.ipfsStorageApiKey); + const { IPFS_STORAGE_API_KEY } = settingsContainer.settings; + + client = new Web3Storage({ token: IPFS_STORAGE_API_KEY } as any); return new MessageResponse({}); } catch (error) { @@ -216,12 +217,13 @@ export async function fileAPI( * @return {any} - settings */ channel.response(MessageAPI.GET_SETTINGS, async (_) => { - const ipfsStorageApiKey = await settingsRepository.findOne({ - name: 'IPFS_STORAGE_API_KEY' - }); + // const {IPFS_STORAGE_API_KEY} = new SettingsContainer().settings; + return new MessageResponse({ - nftApiKey: ipfsStorageApiKey?.value || process.env.IPFS_STORAGE_API_KEY, - ipfsStorageApiKey: ipfsStorageApiKey?.value || process.env.IPFS_STORAGE_API_KEY + // nftApiKey: IPFS_STORAGE_API_KEY, + nftApiKey: '', + // ipfsStorageApiKey: IPFS_STORAGE_API_KEY + ipfsStorageApiKey: '' }); }) } diff --git a/ipfs-client/src/app.ts b/ipfs-client/src/app.ts index cd486f76f7..788052cad8 100644 --- a/ipfs-client/src/app.ts +++ b/ipfs-client/src/app.ts @@ -1,51 +1,31 @@ import { ApplicationStates } from '@guardian/interfaces'; import { Web3Storage } from 'web3.storage'; -import { MessageBrokerChannel, ApplicationState, Logger, DB_DI, DataBaseHelper, Migration, COMMON_CONNECTION_CONFIG } from '@guardian/common'; +import { + MessageBrokerChannel, + ApplicationState, + Logger, + SettingsContainer +} from '@guardian/common'; import { fileAPI } from './api/file.service'; -import { Settings } from './entity/settings'; -import { MikroORM } from '@mikro-orm/core'; -import { MongoDriver } from '@mikro-orm/mongodb'; Promise.all([ - Migration({ - ...COMMON_CONNECTION_CONFIG, - migrations: { - path: 'dist/migrations', - transactional: false - } - }), - MikroORM.init({ - ...COMMON_CONNECTION_CONFIG, - driverOptions: { - useUnifiedTopology: true - }, - ensureIndexes: true - }), MessageBrokerChannel.connect('IPFS_CLIENT') ]).then(async values => { - const [_, db, cn] = values; - DB_DI.orm = db; + const [cn] = values; const state = new ApplicationState('IPFS_CLIENT'); const channel = new MessageBrokerChannel(cn, 'ipfs-client'); new Logger().setChannel(channel); state.setChannel(channel); - // Check configuration - if (!process.env.IPFS_STORAGE_API_KEY || process.env.IPFS_STORAGE_API_KEY.length < 20) { - await new Logger().error('You need to fill IPFS_STORAGE_API_KEY field in .env file', ['IPFS_CLIENT']); - throw new Error('You need to fill IPFS_STORAGE_API_KEY field in .env file'); - } - /////////////// + const settingsContainer = new SettingsContainer(); + settingsContainer.setChannel(channel); + await settingsContainer.init('IPFS_STORAGE_API_KEY'); - state.updateState(ApplicationStates.STARTED); - const settingsRepository = new DataBaseHelper(Settings); - const ipfsStorageApiKey = await settingsRepository.findOne({ - name: 'IPFS_STORAGE_API_KEY' - }); + const {IPFS_STORAGE_API_KEY} = settingsContainer.settings; state.updateState(ApplicationStates.INITIALIZING); - await fileAPI(channel, new Web3Storage({ token: ipfsStorageApiKey?.value || process.env.IPFS_STORAGE_API_KEY } as any), settingsRepository); + await fileAPI(channel, new Web3Storage({ token: IPFS_STORAGE_API_KEY } as any)); state.updateState(ApplicationStates.READY); new Logger().info('ipfs-client service started', ['IPFS_CLIENT']); diff --git a/ipfs-client/src/entity/settings.ts b/ipfs-client/src/entity/settings.ts deleted file mode 100644 index 1556307bfa..0000000000 --- a/ipfs-client/src/entity/settings.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { BaseEntity } from '@guardian/common'; -import { Entity, Property, Unique } from '@mikro-orm/core'; - -/** - * Service settings - */ -@Entity() -@Unique({ properties: ['name'], options: { partialFilterExpression: { name: { $type: 'string' }}}}) -export class Settings extends BaseEntity { - - /** - * Setting name - */ - @Property({ nullable: true }) - name?: string; - - /** - * Setting value - */ - @Property({ nullable: true }) - value?: string; -} diff --git a/logger-service/package.json b/logger-service/package.json index 88be91d367..f031b686c4 100644 --- a/logger-service/package.json +++ b/logger-service/package.json @@ -1,8 +1,8 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", - "@guardian/interfaces": "^2.4.2", + "@guardian/common": "^2.5.0-prerelease", + "@guardian/interfaces": "^2.5.0-prerelease", "@mikro-orm/core": "^5.3.0", "@mikro-orm/mongodb": "^5.3.0", "@web-std/fetch": "3.0.0", @@ -36,5 +36,6 @@ "start": "node dist/index.js", "watch": "nodemon src/index.ts" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/mrv-sender/package.json b/mrv-sender/package.json index 08f8bb8c3e..2326a8a5f4 100644 --- a/mrv-sender/package.json +++ b/mrv-sender/package.json @@ -1,7 +1,7 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", + "@guardian/common": "^2.5.0-prerelease", "@transmute/credentials-context": "0.7.0-unstable.40", "@transmute/did-context": "0.7.0-unstable.40", "@transmute/ed25519-signature-2018": "0.7.0-unstable.40", @@ -29,5 +29,6 @@ "dev:docker": "nodemon .", "start": "node dist/index.js" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/package.json b/package.json index 8cea2215fd..938a937c2c 100644 --- a/package.json +++ b/package.json @@ -14,5 +14,6 @@ "mrv-sender", "worker-service" ], - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/topic-viewer/package.json b/topic-viewer/package.json index f2dba9430d..6dbbf71de2 100644 --- a/topic-viewer/package.json +++ b/topic-viewer/package.json @@ -19,5 +19,5 @@ "dev": "tsc -w", "start": "node dist/index.js" }, - "version": "2.4.2" + "version": "2.5.0-prerelease" } diff --git a/worker-service/.env b/worker-service/.env index 652a0efb8f..761f83d4bf 100644 --- a/worker-service/.env +++ b/worker-service/.env @@ -4,3 +4,5 @@ MIN_PRIORITY="0" MAX_PRIORITY="19" TASK_TIMEOUT="300" REFRESH_INTERVAL="60" +IPFS_TIMEOUT="180" +IPFS_STORAGE_API_KEY="..." diff --git a/worker-service/.env.docker b/worker-service/.env.docker index 0672a51ebd..756e9c6a7c 100644 --- a/worker-service/.env.docker +++ b/worker-service/.env.docker @@ -4,3 +4,5 @@ MIN_PRIORITY="0" MAX_PRIORITY="19" TASK_TIMEOUT="300" REFRESH_INTERVAL="60" +IPFS_TIMEOUT="180" +IPFS_STORAGE_API_KEY="..." diff --git a/worker-service/package.json b/worker-service/package.json index e6da8eb43d..1e1f392ec3 100644 --- a/worker-service/package.json +++ b/worker-service/package.json @@ -1,8 +1,8 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.4.2", - "@guardian/interfaces": "^2.4.2", + "@guardian/common": "^2.5.0-prerelease", + "@guardian/interfaces": "^2.5.0-prerelease", "@hashgraph/sdk": "^2.15.0", "@transmute/credentials-context": "^0.7.0-unstable.60", "@transmute/did-context": "^0.7.0-unstable.60", @@ -12,9 +12,12 @@ "@transmute/security-context": "^0.7.0-unstable.60", "@transmute/vc.js": "^0.7.0-unstable.60", "axios": "^0.25.0", + "axios-retry": "^3.2.4", + "cross-blob": "^2.0.1", "dotenv": "^16.0.0", "module-alias": "^2.2.2", - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "web3.storage": "^4.3.0" }, "description": "", "devDependencies": { @@ -40,7 +43,9 @@ "dev:docker": "nodemon .", "dev": "tsc -w", "lint": "tslint --config ../tslint.json --project .", - "start": "node dist/index.js" + "start": "node dist/index.js", + "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/worker-service.xml" }, - "version": "2.4.2" + "version": "2.5.0-prerelease", + "stableVersion": "2.4.2" } diff --git a/worker-service/src/api/helpers/hedera-sdk-helper.ts b/worker-service/src/api/helpers/hedera-sdk-helper.ts index f1dcd2385f..a5a80304dc 100644 --- a/worker-service/src/api/helpers/hedera-sdk-helper.ts +++ b/worker-service/src/api/helpers/hedera-sdk-helper.ts @@ -31,17 +31,45 @@ import { import { timeout } from './utils'; import axios from 'axios'; import { Environment } from './environment'; -import { TransactionLogger } from './transaction-logger'; import { GenerateUUIDv4 } from '@guardian/interfaces'; import Long from 'long'; export const MAX_FEE = 10; export const INITIAL_BALANCE = 30; +/** + * Transaction event logger data + */ +export interface ITransactionLoggerData { + /** + * Transaction log event type + */ + type: string; + + /** + * Transaction log event data + */ + data: unknown; +} + /** * Contains methods to simplify work with hashgraph sdk */ export class HederaSDKHelper { + /** + * Send transaction log message + * @private + */ + private static sendTransactionLogMessage: (data: ITransactionLoggerData) => Promise + + /** + * Set transaction log function + * @param fn + */ + public static setTransactionLogSender(fn: (data: ITransactionLoggerData) => Promise): void { + HederaSDKHelper.sendTransactionLogMessage = fn; + } + /** * Client * @private @@ -82,7 +110,16 @@ export class HederaSDKHelper { * @private */ private async transactionStartLog(id: string, transactionName: string): Promise { - await TransactionLogger.transactionLog(id, this.client.operatorAccountId, transactionName); + if (HederaSDKHelper.sendTransactionLogMessage) { + await HederaSDKHelper.sendTransactionLogMessage({ + type: 'start-log', + data: { + id, + operatorAccountId: this.client.operatorAccountId?.toString(), + transactionName + } + }); + } } /** @@ -94,7 +131,18 @@ export class HederaSDKHelper { * @private */ private async transactionEndLog(id: string, transactionName: string, transaction?: Transaction, metadata?: any): Promise { - await TransactionLogger.transactionLog(id, this.client.operatorAccountId, transactionName, transaction, metadata); + if (HederaSDKHelper.sendTransactionLogMessage) { + await HederaSDKHelper.sendTransactionLogMessage({ + type: 'end-log', + data: { + id, + operatorAccountId: this.client.operatorAccountId?.toString(), + transactionName, + transaction, + metadata + } + }); + } } /** @@ -106,7 +154,18 @@ export class HederaSDKHelper { * @private */ private async transactionErrorLog(id: string, transactionName: string, transaction: Transaction, error: Error): Promise { - await TransactionLogger.transactionErrorLog(id, this.client.operatorAccountId, transactionName, transaction, error.message); + if (HederaSDKHelper.sendTransactionLogMessage) { + await HederaSDKHelper.sendTransactionLogMessage({ + type: 'error-log', + data: { + id, + operatorAccountId: this.client.operatorAccountId?.toString(), + transactionName, + transaction, + error: error.message + } + }); + } } /** @@ -117,7 +176,16 @@ export class HederaSDKHelper { * @private */ private async virtualTransactionLog(id: string, type: string, client: Client): Promise { - await TransactionLogger.virtualTransactionLog(id, type, client.operatorAccountId?.toString()); + if (HederaSDKHelper.sendTransactionLogMessage) { + await HederaSDKHelper.sendTransactionLogMessage({ + type: 'virtual-function-log', + data: { + id, + operatorAccountId: this.client.operatorAccountId?.toString(), + type + } + }); + } } /** diff --git a/worker-service/src/api/helpers/transaction-logger.ts b/worker-service/src/api/helpers/transaction-logger.ts deleted file mode 100644 index 484f80cee6..0000000000 --- a/worker-service/src/api/helpers/transaction-logger.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { - AccountCreateTransaction, - AccountId, - TokenAssociateTransaction, - TokenCreateTransaction, - TokenDissociateTransaction, - TokenFreezeTransaction, - TokenGrantKycTransaction, - TokenMintTransaction, - TokenRevokeKycTransaction, - TokenUnfreezeTransaction, - TokenWipeTransaction, - TopicCreateTransaction, - TopicMessageSubmitTransaction, - Transaction, - TransferTransaction -} from '@hashgraph/sdk'; -import { HederaSDKHelper } from './hedera-sdk-helper'; - -/** - * Transaction log level - */ -export enum TransactionLogLvl { - NONE = '0', - TRANSACTION = '1', - DEBUG = '2' -} - -/** - * Transaction logger - */ -export class TransactionLogger { - /** - * Log level - * @private - */ - private static logLvl: TransactionLogLvl = TransactionLogLvl.NONE; - /** - * Callback - * @private - */ - private static fn: Function = null; - - /** - * Callback - * @private - */ - private static virtualFileCallback: Function = null; - - /** - * Callback - * @private - */ - private static virtualTransactionCallback: Function = null; - - /** - * Time map - * @private - */ - private static readonly map = {}; - - /** - * Set log level - * @param lvl - */ - public static setLogLevel(lvl: TransactionLogLvl): void { - TransactionLogger.logLvl = lvl || TransactionLogLvl.NONE; - } - - /** - * Set log function - * @param fn - */ - public static setLogFunction(fn: Function): void { - TransactionLogger.fn = fn; - } - - /** - * Set virtual file function - * @param fn - */ - public static setVirtualFileFunction(fn: Function): void { - TransactionLogger.virtualFileCallback = fn; - } - - /** - * Set virtual transaction function - * @param fn - */ - public static setVirtualTransactionFunction(fn: Function): void { - TransactionLogger.virtualTransactionCallback = fn; - } - - /** - * Create log message - * @param types - * @param duration - * @param name - * @param attr - * @private - */ - private static log(types: string[], duration: number, name: string, attr?: string[]) { - const date = (new Date()).toISOString(); - const d = duration ? `${(duration / 1000)}s` : '_'; - const attribute = attr || []; - if (TransactionLogger.fn) { - TransactionLogger.fn(types, date, d, name, attribute); - } - } - - /** - * String size - * @param text - * @private - */ - private static stringSize(text: string | Uint8Array): number { - if (typeof text === 'string') { - const array = Buffer.from(text, 'utf8'); - return array.length; - } else { - return text.length; - } - } - - /** - * Message log - * @param id - * @param name - */ - public static async messageLog(id: string, name: string): Promise { - try { - if (TransactionLogger.logLvl === TransactionLogLvl.NONE) { - return; - } - const time = Date.now(); - const start = TransactionLogger.map[id]; - TransactionLogger.map[id] = time; - - if (start) { - const duration = time - start; - TransactionLogger.log(['MESSAGE', 'COMPLETION'], duration, name, [id]); - } else { - TransactionLogger.log(['MESSAGE', 'SEND'], null, name, [id]); - } - } catch (error) { - TransactionLogger.log(['MESSAGE', 'ERROR'], null, name, [id, error.message]); - } - } - - /** - * Transaction log - * @param id - * @param operatorAccountId - * @param transactionName - * @param transaction - * @param metadata - */ - public static async transactionLog( - id: string, operatorAccountId: AccountId, transactionName: string, transaction?: Transaction, metadata?: any - ): Promise { - try { - if (TransactionLogger.logLvl === TransactionLogLvl.NONE) { - return; - } - const time = Date.now(); - const start = TransactionLogger.map[id]; - TransactionLogger.map[id] = time; - - const account = operatorAccountId.toString(); - const data = TransactionLogger.getTransactionData(transactionName, transaction, metadata); - const attr = [id, account, data]; - - if (TransactionLogger.logLvl === TransactionLogLvl.DEBUG) { - try { - const client = new HederaSDKHelper(process.env.OPERATOR_ID, process.env.OPERATOR_KEY); - const balance = await client.balance(operatorAccountId); - attr.push(balance); - } catch (error) { - attr.push(null); - } - } - - if (transaction) { - const duration = time - start; - TransactionLogger.log(['TRANSACTION', 'COMPLETION'], duration, transactionName, attr); - } else { - TransactionLogger.log(['TRANSACTION', 'CREATE'], null, transactionName, attr); - } - } catch (error) { - TransactionLogger.log(['TRANSACTION', 'ERROR'], null, transactionName, [id, error.message]); - } - } - - /** - * Get transaction data - * @param transactionName - * @param transaction - * @param metadata - * @private - */ - private static getTransactionData(transactionName: string, transaction: Transaction, metadata?: any): string { - let data = ''; - if (!transaction) { - return data; - } - if (transactionName === 'TokenCreateTransaction') { - const t = transaction as TokenCreateTransaction; - data += 'payer sigs: 1; '; - data += `admin keys: ${t.adminKey ? 1 : 0}; `; - data += `KYC keys: ${t.kycKey ? 1 : 0}; `; - data += `wipe keys: ${t.wipeKey ? 1 : 0}; `; - data += `pause keys: ${t.pauseKey ? 1 : 0}; `; - data += `supply keys: ${t.supplyKey ? 1 : 0}; `; - data += `freeze keys: ${t.freezeKey ? 1 : 0}; `; - data += `token name size: ${TransactionLogger.stringSize(t.tokenName)}; `; - data += `token symbol size: ${TransactionLogger.stringSize(t.tokenSymbol)}; `; - data += `token memo size: ${TransactionLogger.stringSize(t.tokenMemo)}; `; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenAssociateTransaction') { - const t = transaction as TokenAssociateTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += 'tokens associated: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenDissociateTransaction') { - const t = transaction as TokenDissociateTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += 'tokens dissociated: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenFreezeTransaction') { - const t = transaction as TokenFreezeTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenUnfreezeTransaction') { - const t = transaction as TokenUnfreezeTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenGrantKycTransaction') { - const t = transaction as TokenGrantKycTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenRevokeKycTransaction') { - const t = transaction as TokenRevokeKycTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenMintTransaction') { - const t = transaction as TokenMintTransaction; - data += 'Fungible Token; '; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenMintNFTTransaction') { - transactionName = 'TokenMintTransaction'; - const t = transaction as TokenMintTransaction; - data += 'Non-Fungible Token; '; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `of NFTs minted: ${t.metadata.length};`; - data += `bytes of metadata per NFT: ${t.metadata[0].length};`; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TokenWipeTransaction') { - const t = transaction as TokenWipeTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TransferTransaction') { - const t = transaction as TransferTransaction; - data += 'Fungible Token; '; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `amount: ${metadata}; `; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'NFTTransferTransaction') { - transactionName = 'TransferTransaction'; - const t = transaction as TransferTransaction; - data += 'Non-Fungible Token; '; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `of NFTs transferred: ${metadata.length}; `; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'AccountCreateTransaction') { - const t = transaction as AccountCreateTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TopicCreateTransaction') { - const t = transaction as TopicCreateTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `admin keys: ${t.adminKey ? 1 : 0}; `; - data += `submit keys: ${t.submitKey ? 1 : 0}; `; - data += `topic memo size: ${TransactionLogger.stringSize(t.topicMemo)}; `; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - if (transactionName === 'TopicMessageSubmitTransaction') { - const t = transaction as TopicMessageSubmitTransaction; - data += 'payer sigs: 1; '; - data += 'total sigs: 1; '; - data += `message size: ${TransactionLogger.stringSize(t.message)}; `; - data += `memo size: ${TransactionLogger.stringSize(t.transactionMemo)}; `; - } - return data; - } - - /** - * Transaction error log - * @param id - * @param operatorAccountId - * @param transactionName - * @param transaction - * @param message - */ - public static async transactionErrorLog( - id: string, operatorAccountId: AccountId, transactionName: string, transaction: Transaction, message: string - ): Promise { - try { - const account = operatorAccountId.toString(); - const attr = [id, account, message]; - TransactionLogger.log(['TRANSACTION', 'ERROR'], null, transactionName, attr); - } catch (error) { - TransactionLogger.log(['TRANSACTION', 'ERROR'], null, transactionName, [error.message]); - } - } - - /** - * Save virtual file log - * @param id - * @param file - */ - public static async virtualFileLog(id:string, file: ArrayBuffer, url:any): Promise { - const date = (new Date()).toISOString(); - if (TransactionLogger.virtualFileCallback) { - TransactionLogger.virtualFileCallback(date, id, file, url); - } - } - - /** - * Save virtual transaction log - * @param id - * @param type - * @param operatorId - */ - public static async virtualTransactionLog(id: string, type: string, operatorId?: string): Promise { - const date = (new Date()).toISOString(); - if (TransactionLogger.virtualTransactionCallback) { - TransactionLogger.virtualTransactionCallback(date, id, type, operatorId); - } - } -} diff --git a/worker-service/src/api/ipfs-client.ts b/worker-service/src/api/ipfs-client.ts new file mode 100644 index 0000000000..51f6d59894 --- /dev/null +++ b/worker-service/src/api/ipfs-client.ts @@ -0,0 +1,60 @@ +import { Web3Storage } from 'web3.storage'; +import Blob from 'cross-blob'; +import axios from 'axios'; + +/** + * AddFileResult + */ +export interface IAddFileResult { + /** + * CID + */ + cid: string; + + /** + * URL + */ + url: string; +} + +/** + * IPFS Client helper + */ +export class IpfsClient { + /** + * Public gateway + */ + private readonly IPFS_PUBLIC_GATEWAY = 'https://ipfs.io/ipfs'; + + /** + * Web3storage instance + * @private + */ + private readonly client: Web3Storage; + + constructor(token: string) { + this.client = new Web3Storage({ token } as any) + } + + /** + * Add file + * @param file + * @param beforeCallback + */ + public async addFiile(file: Blob): Promise { + const cid = await this.client.put([file] as any, { wrapWithDirectory: false }); + const url = `${this.IPFS_PUBLIC_GATEWAY}/${cid}`; + + return { cid, url }; + + } + + /** + * Get file + * @param cid + */ + public async getFile(cid: string): Promise { + const fileRes = await axios.get(`${this.IPFS_PUBLIC_GATEWAY}/${cid}`, { responseType: 'arraybuffer', timeout: parseInt(process.env.IPFS_TIMEOUT, 10) * 1000 || 120000 }); + return fileRes.data; + } +} diff --git a/worker-service/src/api/worker.ts b/worker-service/src/api/worker.ts index 2c580bfc7d..6f0ccfbe37 100644 --- a/worker-service/src/api/worker.ts +++ b/worker-service/src/api/worker.ts @@ -1,6 +1,6 @@ -import { Logger, MessageBrokerChannel } from '@guardian/common'; +import { Logger, MessageBrokerChannel, SettingsContainer } from '@guardian/common'; import { - IAddFileMessage, + ExternalMessageEvents, ITask, ITaskResult, IWorkerRequest, @@ -9,6 +9,26 @@ import { } from '@guardian/interfaces'; import { HederaSDKHelper } from './helpers/hedera-sdk-helper'; import { Environment } from './helpers/environment'; +import { IpfsClient } from './ipfs-client'; +import Blob from 'cross-blob'; +import { HederaUtils } from './helpers/utils'; +import { PrivateKey } from '@hashgraph/sdk'; + +/** + * Split chunk + * @param array + * @param chunk + * @return + */ +function splitChunk(array: T[], chunk: number): T[][] { + const res: T[][] = []; + let i: number; + let j: number; + for (i = 0, j = array.length; i < j; i += chunk) { + res.push(array.slice(i, i + chunk)); + } + return res; +} /** * Sleep helper @@ -30,7 +50,13 @@ export class Worker { * Logger instance * @private */ - private logger: Logger; + private readonly logger: Logger; + + /** + * Ipfs client + * @private + */ + private readonly ipfsClient: IpfsClient; /** * Current task ID @@ -92,7 +118,11 @@ export class Worker { constructor( private readonly channel: MessageBrokerChannel ) { + const { IPFS_STORAGE_API_KEY } = new SettingsContainer().settings; + this.logger = new Logger(); + this.ipfsClient = new IpfsClient(IPFS_STORAGE_API_KEY); + this.minPriority = parseInt(process.env.MIN_PRIORITY, 10); this.maxPriority = parseInt(process.env.MAX_PRIORITY, 10); this.taskTimeout = parseInt(process.env.TASK_TIMEOUT, 10) * 1000; // env in seconds @@ -116,6 +146,19 @@ export class Worker { this.updateEventReceived = true; } }); + + HederaSDKHelper.setTransactionResponseCallback(async (client: any) => { + try { + const balance = await HederaSDKHelper.balance(client, client.operatorAccountId); + await this.channel.request(['api-gateway', 'update-user-balance'].join('.'), { + balance, + unit: 'Hbar', + operatorAccountId: client.operatorAccountId.toString() + }); + } catch (error) { + throw new Error(`Worker (${['api-gateway', 'update-user-balance'].join('.')}) send: ` + error); + } + }) } /** @@ -161,20 +204,345 @@ export class Worker { try { switch (task.type) { - case WorkerTaskType.GET_FILE: - case WorkerTaskType.ADD_FILE: - result.data = await this.channel.request(task.data.target, task.data.payload); + case WorkerTaskType.ADD_FILE: { + let fileContent = Buffer.from(task.data.payload.content, 'base64'); + const data = await this.channel.request(ExternalMessageEvents.IPFS_BEFORE_UPLOAD_CONTENT, task.data.payload); + if (data && data.body) { + fileContent = Buffer.from(data.body, 'base64') + } + const blob: any = new Blob([fileContent]); + const r = await this.ipfsClient.addFiile(blob); + this.channel.publish(ExternalMessageEvents.IPFS_ADDED_FILE, r); + result.data = r; break; + } - case WorkerTaskType.SEND_HEDERA: + case WorkerTaskType.GET_FILE: { + if (!task.data.payload || !task.data.payload.cid || !task.data.payload.responseType) { + result.error = 'Invalid CID'; + } else { + let fileContent = await this.ipfsClient.getFile(task.data.payload.cid); + if (fileContent instanceof Buffer) { + const data = await this.channel.request(ExternalMessageEvents.IPFS_AFTER_READ_CONTENT, { + responseType: !task.data.payload.responseType, + content: fileContent.toString('base64'), + }); + if (data && data.body) { + fileContent = Buffer.from(data.body, 'base64') + } + } + + switch (task.data.payload.responseType) { + case 'str': + result.data = Buffer.from(fileContent, 'binary').toString(); + break; + + case 'json': + result.data = Buffer.from(fileContent, 'binary').toJSON(); + break; + + default: + result.data = fileContent + } + } + break; + } + + case WorkerTaskType.SEND_HEDERA: { Environment.setNetwork(task.data.network); Environment.setLocalNodeAddress(task.data.localNodeAddress); Environment.setLocalNodeProtocol(task.data.localNodeProtocol); - const { operatorId, operatorKey, dryRun } = task.data.clientOptions; + const {operatorId, operatorKey, dryRun} = task.data.clientOptions; const client = new HederaSDKHelper(operatorId, operatorKey, dryRun); - const { topicId, buffer, submitKey, memo } = task.data; + const {topicId, buffer, submitKey, memo} = task.data; result.data = await client.submitMessage(topicId, buffer, submitKey, memo); break; + } + + case WorkerTaskType.GENERATE_DEMO_KEY: { + const {operatorId, operatorKey, initialBalance} = task.data; + const client = new HederaSDKHelper(operatorId, operatorKey); + const treasury = await client.newAccount(initialBalance); + result.data = { + id: treasury.id.toString(), + key: treasury.key.toString() + }; + break; + } + + case WorkerTaskType.GET_USER_BALANCE: { + const {hederaAccountId, hederaAccountKey} = task.data; + const client = new HederaSDKHelper(hederaAccountId, hederaAccountKey); + result.data = await client.balance(hederaAccountId); + + break; + } + + case WorkerTaskType.GET_ACCOUNT_INFO: { + const {userID, userKey, hederaAccountId} = task.data; + const client = new HederaSDKHelper(userID, userKey); + result.data = await client.accountInfo(hederaAccountId); + + break; + } + + case WorkerTaskType.CREATE_TOKEN: { + const { + operatorId, + operatorKey, + changeSupply, + decimals, + enableAdmin, + enableFreeze, + enableKYC, + enableWipe, + initialSupply, + tokenName, + tokenSymbol, + tokenType} = task.data; + const client = new HederaSDKHelper(operatorId, operatorKey); + const treasury = client.newTreasury(operatorId, operatorKey); + const treasuryId = treasury.id; + const treasuryKey = treasury.key; + const adminKey = enableAdmin ? treasuryKey : null; + const kycKey = enableKYC ? treasuryKey : null; + const freezeKey = enableFreeze ? treasuryKey : null; + const wipeKey = enableWipe ? treasuryKey : null; + const supplyKey = changeSupply ? treasuryKey : null; + const nft = tokenType === 'non-fungible'; + const _decimals = nft ? 0 : decimals; + const _initialSupply = nft ? 0 : initialSupply; + const tokenId = await client.newToken( + tokenName, + tokenSymbol, + nft, + _decimals, + _initialSupply, + '', + treasury, + adminKey, + kycKey, + freezeKey, + wipeKey, + supplyKey, + ); + + result.data = { + tokenId, + tokenName, + tokenSymbol, + tokenType, + decimals: _decimals, + initialSupply: _initialSupply, + adminId: treasuryId ? treasuryId.toString() : null, + adminKey: adminKey ? adminKey.toString() : null, + kycKey: kycKey ? kycKey.toString() : null, + freezeKey: freezeKey ? freezeKey.toString() : null, + wipeKey: wipeKey ? wipeKey.toString() : null, + supplyKey: supplyKey ? supplyKey.toString() : null + } + + break; + } + + case WorkerTaskType.NEW_TOKEN: { + const { + operatorId, + operatorKey, + tokenName, + tokenSymbol, + nft, + decimals, + initialSupply, + tokenMemo, + adminKey, + kycKey, + freezeKey, + wipeKey, + supplyKey, + } = task.data; + const client = new HederaSDKHelper(operatorId, operatorKey); + result.data = await client.newToken( + tokenName, + tokenSymbol, + nft, + decimals, + initialSupply, + tokenMemo, + { + id: operatorId, + key: PrivateKey.fromString(operatorKey) + }, + PrivateKey.fromString(adminKey), + PrivateKey.fromString(kycKey), + PrivateKey.fromString(freezeKey), + PrivateKey.fromString(wipeKey), + PrivateKey.fromString(supplyKey), + ); + + break; + } + + case WorkerTaskType.ASSOCIATE_TOKEN: { + const {userID, userKey, associate, tokenId, dryRun} = task.data; + const client = new HederaSDKHelper(userID, userKey, dryRun); + if (associate) { + result.data = await client.associate(tokenId, userID, userKey); + } else { + result.data = await client.dissociate(tokenId, userID, userKey); + } + + break; + } + + case WorkerTaskType.GRANT_KYC_TOKEN: { + const {hederaAccountId, hederaAccountKey, userHederaAccountId, tokenId, kycKey, grant, dryRun} = task.data; + const client = new HederaSDKHelper(hederaAccountId, hederaAccountKey, dryRun); + + if (grant) { + result.data = await client.grantKyc(tokenId, userHederaAccountId, kycKey); + } else { + result.data = await client.revokeKyc(tokenId, userHederaAccountId, kycKey); + } + + break; + } + + case WorkerTaskType.FREEZE_TOKEN: { + const {hederaAccountId, hederaAccountKey, freezeKey, freeze, tokenId, dryRun} = task.data; + const client = new HederaSDKHelper(hederaAccountId, hederaAccountKey, dryRun); + if (freeze) { + result.data = await client.freeze(tokenId, hederaAccountId, freezeKey); + } else { + result.data = await client.unfreeze(tokenId, hederaAccountId, freezeKey); + } + + break; + } + + case WorkerTaskType.MINT_TOKEN: { + const {hederaAccountId, hederaAccountKey, token, tokenValue, dryRun, transactionMemo, uuid, targetAccount, mintId} = task.data; + + const client = new HederaSDKHelper(hederaAccountId, hederaAccountKey, dryRun); + const tokenId = token.tokenId; + const supplyKey = token.supplyKey; + const adminId = token.adminId; + const adminKey = token.adminKey; + + if (token.tokenType === 'non-fungible') { + const metaData = HederaUtils.decode(uuid); + const data = new Array(Math.floor(tokenValue)); + data.fill(metaData); + const serials: number[] = []; + const dataChunk = splitChunk(data, 10); + for (let i = 0; i < dataChunk.length; i++) { + const element = dataChunk[i]; + if (i % 100 === 0) { + console.log(`Mint(${mintId}): Minting (Chunk: ${i + 1}/${dataChunk.length})`); + } + try { + const newSerials = await client.mintNFT(tokenId, supplyKey, element, transactionMemo); + for (const serial of newSerials) { + serials.push(serial) + } + } catch (error) { + console.error(`Mint(${mintId}): Mint Error (${error.message})`); + } + } + + console.log(`Mint(${mintId}): Minted (Count: ${serials.length})`); + console.log(`Mint(${mintId}): Transfer ${adminId} -> ${targetAccount} `); + + const serialsChunk = splitChunk(serials, 10); + for (let i = 0; i < serialsChunk.length; i++) { + const element = serialsChunk[i]; + if (i % 100 === 0) { + console.log(`Mint(${mintId}): Transfer (Chunk: ${i + 1}/${serialsChunk.length})`); + } + try { + await client.transferNFT(tokenId, targetAccount, adminId, adminKey, element, transactionMemo); + } catch (error) { + console.error(`Mint(${mintId}): Transfer Error (${error.message})`); + } + } + } else { + await client.mint(tokenId, supplyKey, tokenValue, transactionMemo); + await client.transfer(tokenId, targetAccount, adminId, adminKey, tokenValue, transactionMemo); + } + + result.data = {} + + break; + } + + case WorkerTaskType.WIPE_TOKEN: { + const { + hederaAccountId, + hederaAccountKey, + targetAccount, + tokenValue, + dryRun, + token, + uuid + } = task.data; + + const tokenId = token.tokenId; + const wipeKey = token.wipeKey; + + const client = new HederaSDKHelper(hederaAccountId, hederaAccountKey , dryRun); + if (token.tokenType === 'non-fungible') { + result.error = 'unsupported operation'; + } else { + await client.wipe(tokenId, targetAccount, wipeKey, tokenValue, uuid); + result.data = {} + } + break; + } + + case WorkerTaskType.NEW_TOPIC: { + const {hederaAccountId, hederaAccountKey , dryRun, topicMemo} = task.data; + const client = new HederaSDKHelper(hederaAccountId, hederaAccountKey , dryRun); + result.data = await client.newTopic( + hederaAccountKey, + hederaAccountKey, + topicMemo + ); + + break; + } + + case WorkerTaskType.GET_TOPIC_MESSAGE: { + const { + operatorId, + operatorKey, + dryRun, + timeStamp + } = task.data; + const client = new HederaSDKHelper(operatorId, operatorKey , dryRun); + result.data = await client.getTopicMessage(timeStamp); + + break; + } + + case WorkerTaskType.GET_TOPIC_MESSAGES: { + const { + operatorId, + operatorKey, + dryRun, + topic + } = task.data; + const client = new HederaSDKHelper(operatorId, operatorKey , dryRun); + result.data = await client.getTopicMessages(topic); + + break; + } + + case WorkerTaskType.CHECK_ACCOUNT: { + const {hederaAccountId} = task.data; + result.data = !HederaSDKHelper.checkAccount(hederaAccountId); + + break; + } default: result.error = 'unknown task' @@ -208,7 +576,7 @@ export class Worker { if (e) { error.error = e.message || e; } - reject(error); + resolve(error); } }) } @@ -255,9 +623,14 @@ export class Worker { const result = await this.processTaskWithTimeout(task); - await this.request(WorkerEvents.TASK_COMPLETE, result); + try { + await this.request(WorkerEvents.TASK_COMPLETE, result); + this.logger.info(`Task completed: ${this.currentTaskId}`, [this._channelName]); + } catch (error) { + this.logger.error(error.message, [this._channelName]); + this.clearState(); - this.logger.info(`Task completed: ${this.currentTaskId}`, [this._channelName]); + } this.getItem().then(); } diff --git a/worker-service/src/app.ts b/worker-service/src/app.ts index a554d9fde7..2c293c087a 100644 --- a/worker-service/src/app.ts +++ b/worker-service/src/app.ts @@ -1,5 +1,6 @@ -import { ApplicationState, Logger, MessageBrokerChannel } from '@guardian/common'; +import { ApplicationState, Logger, MessageBrokerChannel, SettingsContainer } from '@guardian/common'; import { Worker } from './api/worker'; +import { HederaSDKHelper } from './api/helpers/hedera-sdk-helper'; Promise.all([ MessageBrokerChannel.connect('WORKERS_SERVICE') @@ -12,6 +13,14 @@ Promise.all([ const state = new ApplicationState(process.env.SERVICE_CHANNEL.toUpperCase()); state.setChannel(channel); + HederaSDKHelper.setTransactionLogSender(async (data) => { + await channel.request(`guardians.transaction-log-event`, data); + }); + + const settingsContainer = new SettingsContainer(); + settingsContainer.setChannel(channel); + await settingsContainer.init('IPFS_STORAGE_API_KEY'); + const w = new Worker(channel); w.init(); diff --git a/guardian-service/tests/network-tests/hedera-sdk-helper.test.js b/worker-service/tests/network-tests/hedera-sdk-helper.test.js similarity index 98% rename from guardian-service/tests/network-tests/hedera-sdk-helper.test.js rename to worker-service/tests/network-tests/hedera-sdk-helper.test.js index 3c58b9109e..1a9f965c0e 100644 --- a/guardian-service/tests/network-tests/hedera-sdk-helper.test.js +++ b/worker-service/tests/network-tests/hedera-sdk-helper.test.js @@ -3,10 +3,10 @@ dotenv.config(); const { HederaSDKHelper -} = require('../../../guardian-service/dist/hedera-modules/hedera-sdk-helper'); +} = require('../../dist/api/helpers/hedera-sdk-helper'); const { HederaUtils -} = require('../../../guardian-service/dist/hedera-modules/utils'); +} = require('../../dist/api/helpers/utils'); const { Client, AccountBalanceQuery } = require('@hashgraph/sdk'); const { expect, assert } = require('chai'); diff --git a/yarn.lock b/yarn.lock index 194310089a..038a4705ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -187,11 +187,11 @@ __metadata: languageName: node linkType: hard -"@guardian/common@^2.4.2, @guardian/common@workspace:common": +"@guardian/common@^2.5.0-prerelease, @guardian/common@workspace:common": version: 0.0.0-use.local resolution: "@guardian/common@workspace:common" dependencies: - "@guardian/interfaces": ^2.4.2 + "@guardian/interfaces": ^2.5.0-prerelease "@mikro-orm/core": ^5.3.0 "@mikro-orm/migrations-mongodb": ^5.3.1 "@mikro-orm/mongodb": ^5.3.0 @@ -209,7 +209,7 @@ __metadata: languageName: unknown linkType: soft -"@guardian/interfaces@^2.4.2, @guardian/interfaces@workspace:interfaces": +"@guardian/interfaces@^2.5.0-prerelease, @guardian/interfaces@workspace:interfaces": version: 0.0.0-use.local resolution: "@guardian/interfaces@workspace:interfaces" dependencies: @@ -1226,6 +1226,13 @@ __metadata: languageName: node linkType: hard +"@types/caseless@npm:*": + version: 0.12.2 + resolution: "@types/caseless@npm:0.12.2" + checksum: 430d15911184ad11e0a8aa21d1ec15fcc93b90b63570c37bf16ebd34457482bfc8de3f5eb6771e0ef986ce183270d4297823b0f492c346255967e78f7292388b + languageName: node + linkType: hard + "@types/connect@npm:*": version: 3.4.35 resolution: "@types/connect@npm:3.4.35" @@ -1336,6 +1343,23 @@ __metadata: languageName: node linkType: hard +"@types/mustache@npm:*": + version: 4.2.1 + resolution: "@types/mustache@npm:4.2.1" + checksum: 27e84f17a32f18ba1bc2fc08360a66687a9436233a6c1b48c6e8d6766644ebbeb2538b7f40edcc760dc0023a8f773cef0d60092870aeef86b1d1e744e4a99480 + languageName: node + linkType: hard + +"@types/node-vault@npm:^0": + version: 0.9.1 + resolution: "@types/node-vault@npm:0.9.1" + dependencies: + "@types/mustache": "*" + "@types/request": "*" + checksum: d22cb1026055732da87bba731664dbde2ed0ee1d246b97a5e34e2253913ca584cba1947bd8412d626d2e0cc57b009d34004a4753b4794c1e0f140230fc024536 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0, @types/node@npm:^17.0.13, @types/node@npm:^17.0.23": version: 17.0.34 resolution: "@types/node@npm:17.0.34" @@ -1371,6 +1395,18 @@ __metadata: languageName: node linkType: hard +"@types/request@npm:*": + version: 2.48.8 + resolution: "@types/request@npm:2.48.8" + dependencies: + "@types/caseless": "*" + "@types/node": "*" + "@types/tough-cookie": "*" + form-data: ^2.5.0 + checksum: 0b7754941e08205dce51635d894ec524df276d2b83ca13b9aab723f9281acecf1108841e9554494cb1cb60f6d6ddbb47ebea97392bcf2bf607f035b3a9b4af45 + languageName: node + linkType: hard + "@types/responselike@npm:^1.0.0": version: 1.0.0 resolution: "@types/responselike@npm:1.0.0" @@ -1407,6 +1443,13 @@ __metadata: languageName: node linkType: hard +"@types/tough-cookie@npm:*": + version: 4.0.2 + resolution: "@types/tough-cookie@npm:4.0.2" + checksum: e055556ffdaa39ad85ede0af192c93f93f986f4bd9e9426efdc2948e3e2632db3a4a584d4937dbf6d7620527419bc99e6182d3daf2b08685e710f2eda5291905 + languageName: node + linkType: hard + "@types/webidl-conversions@npm:*": version: 6.1.1 resolution: "@types/webidl-conversions@npm:6.1.1" @@ -1762,8 +1805,8 @@ __metadata: version: 0.0.0-use.local resolution: "api-gateway@workspace:api-gateway" dependencies: - "@guardian/common": ^2.4.2 - "@guardian/interfaces": ^2.4.2 + "@guardian/common": ^2.5.0-prerelease + "@guardian/interfaces": ^2.5.0-prerelease "@types/express": ^4.17.13 "@types/jszip": ^3.4.1 "@types/node": ^17.0.13 @@ -1941,12 +1984,13 @@ __metadata: version: 0.0.0-use.local resolution: "auth-service@workspace:auth-service" dependencies: - "@guardian/common": ^2.4.2 - "@guardian/interfaces": ^2.4.2 + "@guardian/common": ^2.5.0-prerelease + "@guardian/interfaces": ^2.5.0-prerelease "@mikro-orm/core": ^5.3.0 "@mikro-orm/mongodb": ^5.3.0 "@types/jsonwebtoken": ^8.5.4 "@types/node": ^17.0.13 + "@types/node-vault": ^0 chai: ^4.3.4 cross-env: ^7.0.3 dotenv: ^16.0.0 @@ -1954,6 +1998,7 @@ __metadata: mocha: ^9.2.0 mocha-junit-reporter: ^2.0.2 module-alias: ^2.2.2 + node-vault: ^0.9.22 nodemon: ^2.0.12 reflect-metadata: ^0.1.13 tslint: ^6.1.3 @@ -2917,6 +2962,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:3.1.0": + version: 3.1.0 + resolution: "debug@npm:3.1.0" + dependencies: + ms: 2.0.0 + checksum: 0b52718ab957254a5b3ca07fc34543bc778f358620c206a08452251eb7fc193c3ea3505072acbf4350219c14e2d71ceb7bdaa0d3370aa630b50da790458d08b3 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.3": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -3877,6 +3931,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^2.5.0": + version: 2.5.1 + resolution: "form-data@npm:2.5.1" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.6 + mime-types: ^2.1.12 + checksum: 5134ada56cc246b293a1ac7678dba6830000603a3979cf83ff7b2f21f2e3725202237cfb89e32bcb38a1d35727efbd3c3a22e65b42321e8ade8eec01ce755d08 + languageName: node + linkType: hard + "form-data@npm:^4.0.0": version: 4.0.0 resolution: "form-data@npm:4.0.0" @@ -4268,8 +4333,8 @@ __metadata: version: 0.0.0-use.local resolution: "guardian-service@workspace:guardian-service" dependencies: - "@guardian/common": ^2.4.2 - "@guardian/interfaces": ^2.4.2 + "@guardian/common": ^2.5.0-prerelease + "@guardian/interfaces": ^2.5.0-prerelease "@hashgraph/sdk": ^2.15.0 "@mikro-orm/core": ^5.3.0 "@mikro-orm/mongodb": ^5.3.0 @@ -4352,7 +4417,7 @@ __metadata: languageName: node linkType: hard -"har-validator@npm:~5.1.3": +"har-validator@npm:~5.1.0, har-validator@npm:~5.1.3": version: 5.1.5 resolution: "har-validator@npm:5.1.5" dependencies: @@ -4839,10 +4904,8 @@ __metadata: version: 0.0.0-use.local resolution: "ipfs-client@workspace:ipfs-client" dependencies: - "@guardian/common": ^2.4.2 - "@guardian/interfaces": ^2.4.2 - "@mikro-orm/core": ^5.3.0 - "@mikro-orm/mongodb": ^5.3.0 + "@guardian/common": ^2.5.0-prerelease + "@guardian/interfaces": ^2.5.0-prerelease "@types/fs-extra": ^9.0.12 "@types/js-yaml": ^4.0.3 "@types/node": ^17.0.13 @@ -5988,7 +6051,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.3.0": +"lodash@npm:^4.17.11, lodash@npm:^4.3.0": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -6022,8 +6085,8 @@ __metadata: version: 0.0.0-use.local resolution: "logger-service@workspace:logger-service" dependencies: - "@guardian/common": ^2.4.2 - "@guardian/interfaces": ^2.4.2 + "@guardian/common": ^2.5.0-prerelease + "@guardian/interfaces": ^2.5.0-prerelease "@mikro-orm/core": ^5.3.0 "@mikro-orm/mongodb": ^5.3.0 "@types/fs-extra": ^9.0.12 @@ -6580,7 +6643,7 @@ __metadata: version: 0.0.0-use.local resolution: "mrv-sender@workspace:mrv-sender" dependencies: - "@guardian/common": ^2.4.2 + "@guardian/common": ^2.5.0-prerelease "@transmute/credentials-context": 0.7.0-unstable.40 "@transmute/did-context": 0.7.0-unstable.40 "@transmute/ed25519-signature-2018": 0.7.0-unstable.40 @@ -6655,6 +6718,15 @@ __metadata: languageName: node linkType: hard +"mustache@npm:^2.2.1": + version: 2.3.2 + resolution: "mustache@npm:2.3.2" + bin: + mustache: ./bin/mustache + checksum: e3073355fe8efc4c36eb18af9985812aa7ab216d2b07246b407320b0f93acebeae14719ade700e817393c27e2168a4a1092d6c2f65efb3e26f0be84747a81e78 + languageName: node + linkType: hard + "mute-stream@npm:0.0.7": version: 0.0.7 resolution: "mute-stream@npm:0.0.7" @@ -6872,6 +6944,19 @@ __metadata: languageName: node linkType: hard +"node-vault@npm:^0.9.22": + version: 0.9.22 + resolution: "node-vault@npm:0.9.22" + dependencies: + debug: 3.1.0 + mustache: ^2.2.1 + request: 2.88.0 + request-promise-native: 1.0.7 + tv4: ^1.2.7 + checksum: 86a820682be560fff9b916367af7697b8331bfae3f4ff6070cf38580238ef5c985e817145c7bdb8b40ae3a0602d4c0767d623182334b7b91eb3de38fe9f7b4e9 + languageName: node + linkType: hard + "nodemon@npm:^2.0.12": version: 2.0.16 resolution: "nodemon@npm:2.0.16" @@ -7468,6 +7553,13 @@ __metadata: languageName: node linkType: hard +"psl@npm:^1.1.24": + version: 1.9.0 + resolution: "psl@npm:1.9.0" + checksum: 20c4277f640c93d393130673f392618e9a8044c6c7bf61c53917a0fddb4952790f5f362c6c730a9c32b124813e173733f9895add8d26f566ed0ea0654b2e711d + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.8.0 resolution: "psl@npm:1.8.0" @@ -7492,6 +7584,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^1.4.1": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: fa6e698cb53db45e4628559e557ddaf554103d2a96a1d62892c8f4032cd3bc8871796cae9eabc1bc700e2b6677611521ce5bb1d9a27700086039965d0cf34518 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1": version: 2.1.1 resolution: "punycode@npm:2.1.1" @@ -7783,6 +7882,58 @@ __metadata: languageName: node linkType: hard +"request-promise-core@npm:1.1.2": + version: 1.1.2 + resolution: "request-promise-core@npm:1.1.2" + dependencies: + lodash: ^4.17.11 + peerDependencies: + request: ^2.34 + checksum: 4946886b8dd9d40c4ea0ca39e25093a52f66d43a6442a30f7978ac45619cb312c067a44aabb300157adef6d6407a5f03daf4f621349a88329ca453c3b6bbd0fa + languageName: node + linkType: hard + +"request-promise-native@npm:1.0.7": + version: 1.0.7 + resolution: "request-promise-native@npm:1.0.7" + dependencies: + request-promise-core: 1.1.2 + stealthy-require: ^1.1.1 + tough-cookie: ^2.3.3 + peerDependencies: + request: ^2.34 + checksum: 92d87afe6d8fb65083880e375d320e3febefc2f8cc91c89aa2bf1c7654b3b4ba14efd54f80a6539368fa63eb389fcef8e73ea51dad151b8f507ad957ed9317a1 + languageName: node + linkType: hard + +"request@npm:2.88.0": + version: 2.88.0 + resolution: "request@npm:2.88.0" + dependencies: + aws-sign2: ~0.7.0 + aws4: ^1.8.0 + caseless: ~0.12.0 + combined-stream: ~1.0.6 + extend: ~3.0.2 + forever-agent: ~0.6.1 + form-data: ~2.3.2 + har-validator: ~5.1.0 + http-signature: ~1.2.0 + is-typedarray: ~1.0.0 + isstream: ~0.1.2 + json-stringify-safe: ~5.0.1 + mime-types: ~2.1.19 + oauth-sign: ~0.9.0 + performance-now: ^2.1.0 + qs: ~6.5.2 + safe-buffer: ^5.1.2 + tough-cookie: ~2.4.3 + tunnel-agent: ^0.6.0 + uuid: ^3.3.2 + checksum: aecf4f8cdb0ebd5feac5e29b748d6ab376ac5717ddcbc5a6bb24cc3808bde755ff0fa3a8379a2d25f6c4b969ced1ac065d22a615c71747cd305731efa643e30d + languageName: node + linkType: hard + "request@npm:^2.88.0": version: 2.88.2 resolution: "request@npm:2.88.2" @@ -8415,6 +8566,13 @@ __metadata: languageName: node linkType: hard +"stealthy-require@npm:^1.1.1": + version: 1.1.1 + resolution: "stealthy-require@npm:1.1.1" + checksum: 6805b857a9f3a6a1079fc6652278038b81011f2a5b22cbd559f71a6c02087e6f1df941eb10163e3fdc5391ab5807aa46758d4258547c1f5ede31e6d9bfda8dd3 + languageName: node + linkType: hard + "stream-to-it@npm:^0.2.2, stream-to-it@npm:^0.2.3": version: 0.2.4 resolution: "stream-to-it@npm:0.2.4" @@ -8779,7 +8937,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:~2.5.0": +"tough-cookie@npm:^2.3.3, tough-cookie@npm:~2.5.0": version: 2.5.0 resolution: "tough-cookie@npm:2.5.0" dependencies: @@ -8789,6 +8947,16 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:~2.4.3": + version: 2.4.3 + resolution: "tough-cookie@npm:2.4.3" + dependencies: + psl: ^1.1.24 + punycode: ^1.4.1 + checksum: af5c7b03f22fc60b7a03339414d7e5b4d68aea84bcc591b4bfab73d85f71e218ff9ebdf94042205051faf980bdb2eeec5c8cf6ea5368fd9f878d2c3f718640b7 + languageName: node + linkType: hard + "tr46@npm:^3.0.0": version: 3.0.0 resolution: "tr46@npm:3.0.0" @@ -8887,6 +9055,13 @@ __metadata: languageName: node linkType: hard +"tv4@npm:^1.2.7": + version: 1.3.0 + resolution: "tv4@npm:1.3.0" + checksum: 075096cf3bc2db5727650e16717a343954625c5fde6b2bb5553c86a9a5ca7b9fd287c0f5ab7ac03094f39e982fe9288dc715c7223a90e1684fd2263460a74bbd + languageName: node + linkType: hard + "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" @@ -9455,8 +9630,8 @@ __metadata: version: 0.0.0-use.local resolution: "worker-service@workspace:worker-service" dependencies: - "@guardian/common": ^2.4.2 - "@guardian/interfaces": ^2.4.2 + "@guardian/common": ^2.5.0-prerelease + "@guardian/interfaces": ^2.5.0-prerelease "@hashgraph/sdk": ^2.15.0 "@transmute/credentials-context": ^0.7.0-unstable.60 "@transmute/did-context": ^0.7.0-unstable.60 @@ -9467,7 +9642,9 @@ __metadata: "@transmute/vc.js": ^0.7.0-unstable.60 "@types/node": ^17.0.13 axios: ^0.25.0 + axios-retry: ^3.2.4 chai: 4.3.4 + cross-blob: ^2.0.1 dotenv: ^16.0.0 mocha: ^9.2.0 mocha-junit-reporter: ^2.0.2 @@ -9477,6 +9654,7 @@ __metadata: rewire: ^6.0.0 tslint: ^6.1.3 typescript: ^4.5.5 + web3.storage: ^4.3.0 languageName: unknown linkType: soft
Username {{element.username}}
-
+
+
+
+
+
+