diff --git a/api-gateway/src/api/service/policy.ts b/api-gateway/src/api/service/policy.ts index 76176c27cd..58261d1f62 100644 --- a/api-gateway/src/api/service/policy.ts +++ b/api-gateway/src/api/service/policy.ts @@ -2,7 +2,7 @@ import { Auth, AuthUser } from '#auth'; import { CACHE, POLICY_REQUIRED_PROPS, PREFIXES } from '#constants'; import { AnyFilesInterceptor, CacheService, EntityOwner, getCacheKey, InternalException, ONLY_SR, PolicyEngine, ProjectService, ServiceError, TaskManager, UploadedFiles, UseCache, parseSavepointIdsJson, FilenameSanitizer } from '#helpers'; import { IAuthUser, PinoLogger, RunFunctionAsync } from '@guardian/common'; -import { DocumentType, Permissions, PolicyHelper, TaskAction, UserRole } from '@guardian/interfaces'; +import { DocumentType, Permissions, PolicyEditableFieldDTO, PolicyHelper, TaskAction, UserRole } from '@guardian/interfaces'; import { Body, Controller, @@ -50,6 +50,7 @@ import { ServiceUnavailableErrorDTO, TaskDTO, ResponseDTOWithSyncEvents, + PolicyParametersDTO, MigrationRunsResponseDTO, MigrationRunStatusDTO, MigrationStatusResponseDTO, @@ -1014,6 +1015,7 @@ export class PolicyApi { model.policyGroups = policy.policyGroups; model.categories = policy.categories; model.projectSchema = policy.projectSchema; + model.editableParametersSettings = policy.editableParametersSettings; const invalidedCacheTags = [`${PREFIXES.POLICIES}${policyId}/navigation`, `${PREFIXES.POLICIES}${policyId}/groups`, `${PREFIXES.SCHEMES}schema-with-sub-schemas`]; await this.cacheService.invalidate(getCacheKey([req.url, ...invalidedCacheTags], user)); @@ -2885,7 +2887,8 @@ export class PolicyApi { Permissions.POLICIES_POLICY_UPDATE, Permissions.POLICIES_POLICY_TAG, Permissions.MODULES_MODULE_UPDATE, - Permissions.TOOLS_TOOL_UPDATE + Permissions.TOOLS_TOOL_UPDATE, + Permissions.POLICIES_POLICY_READ // UserRole.STANDARD_REGISTRY, ) @ApiOperation({ @@ -5171,5 +5174,82 @@ export class PolicyApi { await InternalException(error, this.logger, user.id); } } + + /** + * Save Parameters Values + */ + @Post('/:policyId/parameters') + @Auth( + Permissions.POLICIES_POLICY_UPDATE, + Permissions.POLICIES_POLICY_EXECUTE, + Permissions.POLICIES_POLICY_MANAGE, + ) + @ApiOperation({ + summary: 'Save policy config with values', + description: 'Save policy config with values to the PolicyParameters tale', + }) + @ApiBody({ + description: 'Policy parameters.', + isArray: true, + type: PolicyEditableFieldDTO, + }) + @ApiOkResponse({ + description: 'Successful operation.' + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyParametersDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async savePolicyParametersValues( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + @Body() body: PolicyEditableFieldDTO[], + ): Promise { + try { + const engineService = new PolicyEngine(); + return await engineService.savePolicyParameters(new EntityOwner(user), user.did, policyId, body ); + } catch (error) { + await InternalException(error, this.logger, user.id); + } + } + + /** + * Get Parameters + */ + @Get('/:policyId/parameters/config') + @Auth( + Permissions.POLICIES_POLICY_READ, + ) + @ApiOperation({ + summary: 'Get policy parameters.', + description: 'Get policy parameters.', + }) + @ApiBody({ + description: 'Policy parameters.', + isArray: true, + type: PolicyEditableFieldDTO, + }) + @ApiOkResponse({ + description: 'Successful operation.' + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(PolicyParametersDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getPolicyParametersConfig( + @AuthUser() user: IAuthUser, + @Param('policyId') policyId: string, + ): Promise { + try { + const engineService = new PolicyEngine(); + return await engineService.getPolicyParametersConfig(new EntityOwner(user), user, policyId ); + } catch (error) { + await InternalException(error, this.logger, user.id); + } + } //#endregion } diff --git a/api-gateway/src/helpers/policy-engine.ts b/api-gateway/src/helpers/policy-engine.ts index 8cb320c388..b7eda6d9dc 100644 --- a/api-gateway/src/helpers/policy-engine.ts +++ b/api-gateway/src/helpers/policy-engine.ts @@ -1,8 +1,9 @@ import { BasePolicyDTO, ExportMessageDTO, PoliciesValidationDTO, PolicyCommentCountDTO, PolicyCommentDTO, PolicyCommentRelationshipDTO, PolicyCommentUserDTO, PolicyDiscussionDTO, PolicyDTO, PolicyPreviewDTO, PolicyRequestCountDTO, PolicyValidationDTO, PolicyVersionDTO, SchemaDTO } from '#middlewares'; import { IAuthUser, NatsService } from '@guardian/common'; -import { DocumentType, GenerateUUIDv4, IOwner, MigrationConfig, PolicyEngineEvents, PolicyToolMetadata } from '@guardian/interfaces'; +import { DocumentType, GenerateUUIDv4, IOwner, MigrationConfig, PolicyEditableFieldDTO, PolicyEngineEvents, PolicyToolMetadata } from '@guardian/interfaces'; import { Singleton } from '../helpers/decorators/singleton.js'; import { NewTask } from './task-manager.js'; +import { PolicyParametersDTO } from 'middlewares/validation/schemas/policy-parameters.dto.js'; /** * Policy engine service @@ -1721,4 +1722,32 @@ export class PolicyEngine extends NatsService { ): Promise { return await this.sendMessage(PolicyEngineEvents.GET_All_NEW_VERSION_VC_DOCUMENTS, { user, policyId, documentId }); } + + /** + * Update policy parameters + * @param user + * @param policyId + * @param data + */ + public async savePolicyParameters( + owner: IOwner, + userDID: string, + policyId: string, + config: PolicyEditableFieldDTO[], + ): Promise { + return await this.sendMessage(PolicyEngineEvents.SAVE_POLICY_PARAMETERS_VALUES, { owner, userDID, policyId, config }); + } + + /** + * Get policy parameters + * @param user + * @param policyId + */ + public async getPolicyParametersConfig( + owner: IOwner, + user: IAuthUser, + policyId: string, + ): Promise { + return await this.sendMessage(PolicyEngineEvents.GET_POLICY_PARAMETERS_VALUES, { owner, user, policyId }); + } } diff --git a/api-gateway/src/middlewares/validation/schemas/index.ts b/api-gateway/src/middlewares/validation/schemas/index.ts index f580ba7adf..c9c4df57b4 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -36,4 +36,5 @@ export * from './formulas.dto.js' export * from './external-policies.dto.js' export * from './schema-deletion.dto.js' export * from './policy-comments.dto.js' -export * from './relayer-account.dto.js' \ No newline at end of file +export * from './relayer-account.dto.js' +export * from './policy-parameters.dto.js' \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/policies.dto.ts b/api-gateway/src/middlewares/validation/schemas/policies.dto.ts index 9167292076..91b6411f55 100644 --- a/api-gateway/src/middlewares/validation/schemas/policies.dto.ts +++ b/api-gateway/src/middlewares/validation/schemas/policies.dto.ts @@ -1,6 +1,6 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import {ArrayNotEmpty, IsArray, IsBoolean, IsIn, IsNumber, IsObject, IsOptional, IsString, ValidateNested} from 'class-validator'; -import { PolicyAvailability, PolicyStatus, PolicyTestStatus } from '@guardian/interfaces'; +import { PolicyAvailability, PolicyEditableFieldDTO, PolicyStatus, PolicyTestStatus } from '@guardian/interfaces'; import { Examples } from '../examples.js'; import { ValidationErrorsDTO } from './blocks.js'; import {Type} from 'class-transformer'; @@ -247,6 +247,14 @@ export class PolicyDTO { @IsBoolean() originalChanged?: boolean; + @ApiProperty({ + type: () => PolicyEditableFieldDTO, + isArray: true + }) + @IsOptional() + @IsArray() + editableParametersSettings?: PolicyEditableFieldDTO[]; + @ApiProperty({ type: 'object', additionalProperties: true, diff --git a/api-gateway/src/middlewares/validation/schemas/policy-parameters.dto.ts b/api-gateway/src/middlewares/validation/schemas/policy-parameters.dto.ts new file mode 100644 index 0000000000..0bbfc1ef3d --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/policy-parameters.dto.ts @@ -0,0 +1,47 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Examples } from '../examples.js'; +import { IsOptional, IsString } from 'class-validator'; +import { PolicyEditableFieldDTO } from '@guardian/interfaces'; + +export class PolicyParametersDTO { + @ApiProperty({ + type: 'string', + required: true, + example: Examples.DB_ID + }) + userId: string; + + @ApiProperty({ + type: 'string', + required: true, + example: Examples.UUID + }) + @IsString() + policyId: string; + + @ApiProperty({ + type: () => PolicyEditableFieldDTO, + required: false, + isArray: true, + }) + @IsString() + config: PolicyEditableFieldDTO[]; + + @ApiProperty({ + type: 'boolean', + required: false, + example: true + }) + @IsString() + @IsOptional() + updated: boolean; + + @ApiProperty({ + type: 'string', + required: false, + example: 'Prop Value' + }) + @IsString() + @IsOptional() + propValue?: string; +} diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index dbfad1c278..2a65575a81 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -58,6 +58,7 @@ import { PolicyDiscussion, GlobalEventsReaderStream, GlobalEventsWriterStream, + PolicyParameters, MigrationMessageMap, MigrationFailedItem, DeleteCache, @@ -962,6 +963,66 @@ export class DatabaseServer extends AbstractDatabaseServer { return await new DataBaseHelper(SchemaRule).remove(rule); } + /** + * Create Policy Parameters + * @param parameters + */ + public static async createPolicyParameters( + parameters: PolicyParameters + ): Promise { + const item = new DataBaseHelper(PolicyParameters).create(parameters); + return await new DataBaseHelper(PolicyParameters).save(item); + } + + /** + * Set flag updated true + * @param parameters + * @param policyId + */ + public static async setPolicyParametersUpdated( + parameters: PolicyParameters[], + ): Promise { + return await new DataBaseHelper(PolicyParameters).updateMany(parameters); + } + + /** + * Update Policy Parameters + * @param label + */ + public static async updatePolicyParameters( + parameters: PolicyParameters + ): Promise { + return await new DataBaseHelper(PolicyParameters).update(parameters); + } + + /** + * Get Policy Parameters + * @param filters + */ + public static async getPolicyParameters( + userDID: string, + policyId: string + ): Promise { + return await new DataBaseHelper(PolicyParameters).findOne({ + userDID, + policyId + }); + } + + /** + * Get Policy Parameters by Policy Id + * @param filters + */ + public static async getPolicyParametersByPolicyId( + policyId: string + ): Promise { + return await new DataBaseHelper(PolicyParameters).findAll({ + where: { + policyId + } + }); + } + /** * Create Policy Label * @param label @@ -4916,6 +4977,7 @@ export class DatabaseServer extends AbstractDatabaseServer { model.policyGroups = data.policyGroups; model.categories = data.categories; model.projectSchema = data.projectSchema; + model.editableParametersSettings = data.editableParametersSettings; return await new DataBaseHelper(Policy).save(model); } diff --git a/common/src/entity/index.ts b/common/src/entity/index.ts index bf79b41ddb..0c6f9f2b7f 100644 --- a/common/src/entity/index.ts +++ b/common/src/entity/index.ts @@ -61,6 +61,7 @@ export * from './policy-keys.js'; export * from './document-draft.js'; export * from './policy-comment.js'; export * from './policy-discussion.js'; +export * from './policy-parameters.js'; export * from './migration-run.js'; export * from './migration-failed-item.js'; export * from './migration-message-map.js'; diff --git a/common/src/entity/policy-parameters.ts b/common/src/entity/policy-parameters.ts new file mode 100644 index 0000000000..20a5e7cbdc --- /dev/null +++ b/common/src/entity/policy-parameters.ts @@ -0,0 +1,39 @@ +import { Entity, Property} from '@mikro-orm/core'; +import { BaseEntity } from '../models/index.js'; +import { PolicyEditableFieldDTO } from '@guardian/interfaces'; + +/** + * PolicyParameters collection + */ +@Entity() +export class PolicyParameters extends BaseEntity { + /** + * User ID + */ + @Property({ nullable: false }) + userDID: string; + + /** + * Policy ID + */ + @Property({ nullable: false }) + policyId: string; + + /** + * Config with value + */ + @Property({ nullable: true }) + config: PolicyEditableFieldDTO[]; + + /** + * Updated flag + */ + @Property({ nullable: false }) + updated: boolean; + + /** + * Config with value + */ + @Property({ nullable: true }) + properties?: any; +} diff --git a/common/src/entity/policy.ts b/common/src/entity/policy.ts index 5271174097..95c374feb9 100644 --- a/common/src/entity/policy.ts +++ b/common/src/entity/policy.ts @@ -297,6 +297,9 @@ export class Policy extends BaseEntity { @Property({ nullable: true }) originalMessageId?: string; + @Property({ nullable: true }) + editableParametersSettings?: any[]; + /** * File id of the original policy zip (publish flow). */ diff --git a/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.html b/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.html new file mode 100644 index 0000000000..7fdbd9dfed --- /dev/null +++ b/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.html @@ -0,0 +1,117 @@ + +
+ + {{ config.shortDescription }} + + +
+
+ + +
+ + {{ config.shortDescription }} + + +
+
+ + +
+ + {{ config.shortDescription }} + + +
+
+ + +
+ + {{ config.shortDescription }} + + +
+
+ + +
+ + {{ config.shortDescription }} + + + +
+
+ + +
+ + {{ config.shortDescription }} + +
+ +
+
+
+ +
+
+ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.scss b/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.scss new file mode 100644 index 0000000000..6ca0a8ab0c --- /dev/null +++ b/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.scss @@ -0,0 +1,84 @@ + +.property-label { + display: block; + font-weight: 700; + font-size: 14px; + margin-bottom: 4px; +} + +.element-container { + margin-bottom: 24px; + border: 1px solid #E1E7EF; + background-color: #F9FAFC; + padding: 16px; + border-radius: 8px; +} +.element-path { + display: flex; + gap: 16px; + + & > * { + flex: 1; + } +} + +.p-inputtext, +.p-dropdown { + width:100%; + border-radius: 8px; +} + +.property-description { + color: #848FA9; + display: block; + font-weight: 500; + font-size: 12px; + margin-bottom: 16px; +} + +.element-array { + display: flex; + flex-direction: column; + gap: 16px; + margin-top: 16px; + + .add-button { + height: 28px; + padding: 6px 16px; + } + +} + +.element-array-group { + align-items: center; + display: flex; + gap: 16px; + + > :not(:last-child) { + flex: 1; + } + + .element-container { + padding: 0; + border: none; + padding-left: 0; + padding-right: 0; + margin-bottom: 0; + } + + .element-array-delete { + position: relative; + top: 10px; + } + + .property-label { + font-weight: 500; + font-size: 12px; + margin-bottom: 6px; + } +} + +.ghost-color { + fill: #AAB7C4; + color: #AAB7C4; +} \ No newline at end of file diff --git a/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.ts b/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.ts new file mode 100644 index 0000000000..8794b0a841 --- /dev/null +++ b/frontend/src/app/components/policy-parameter-property/policy-parameter-property.component.ts @@ -0,0 +1,121 @@ +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewEncapsulation, } from '@angular/core'; +import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms'; +import { PolicyEditableFieldDTO } from '@guardian/interfaces'; +import { DialogService } from 'primeng/dynamicdialog'; +import { CodeEditorDialogComponent } from 'src/app/modules/policy-engine/dialogs/code-editor-dialog/code-editor-dialog.component'; +import { PolicyBlock } from 'src/app/modules/policy-engine/structures'; + +/** + * policy parameter property + */ + +@Component({ + selector: '[policy-parameter-property]', + templateUrl: './policy-parameter-property.component.html', + styleUrls: ['./policy-parameter-property.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class PolicyParameterPropertyComponent implements OnInit { + @Input() control!: AbstractControl; + @Input('block') currentBlock?: PolicyBlock; + @Input('property') property!: any; + @Input('collapse') collapse!: any; + @Input('readonly') readonly!: boolean; + @Input('config') config: PolicyEditableFieldDTO; + @Input('offset') offset!: number; + @Output('update') update = new EventEmitter(); + @Input('isArrayElement') isArrayElement?: boolean; + + rootPath: string; + pathValue: string; + + needUpdate: boolean = true; + + allBlocks: any[] = []; + childrenBlocks: any[] = []; + loaded: boolean = false; + + constructor( + private dialog: DialogService, + ) { + } + + get isArray(): boolean { return this.control instanceof FormArray; } + get fc(): FormControl { return this.control as FormControl; } + + + get rows(): AbstractControl[] { + return (this.control as FormArray).controls; + } + + get group(): FormGroup { + return this.control as FormGroup; + } + + addItems() { + this.needUpdate = true; + const rows = this.control as FormArray; + const group: Record = {}; + for (const p of this.property?.items?.properties ?? []) { + group[p.name] = new FormControl('', []); + } + + rows.push(new FormGroup(group)); + this.update.emit(); + } + + rowGroup(row: AbstractControl): FormGroup { + return row as FormGroup; + } + + removeItems(index: number) { + const rows = this.control as FormArray; + + if (!rows || index < 0 || index >= rows.length) return; + + rows.removeAt(index); + this.needUpdate = true; + this.update.emit(); + } + + ngOnInit(): void { + this.needUpdate = true; + + if (this.property?.type === 'Array') { + if (!Array.isArray(this.config.value)) { + this.config.value = []; + } + } + } + + onPathPropertyChanged() { + this.onSave(); + } + + onSave() { + this.needUpdate = true; + this.update.emit(); + } + + editCode($event: MouseEvent) { + const dialogRef = this.dialog.open(CodeEditorDialogComponent, { + showHeader: false, + width: '90%', + styleClass: 'guardian-dialog', + data: { + test: false, + expression: this.fc.value, + readonly: this.readonly + } + }) + dialogRef.onClose.subscribe(result => { + if (result) { + this.fc.setValue(result.expression); + if (result.type === 'save') { + this.needUpdate = true; + this.update.emit(); + } + } + }) + } +} diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.html b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.html new file mode 100644 index 0000000000..c95987d5b6 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.html @@ -0,0 +1,115 @@ +
+
+
+
+
+
+
+ + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+ + +
+
+ + +
+
+ + + + +
+
+
+
+
+ +
+ +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.scss b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.scss new file mode 100644 index 0000000000..8f5062e38d --- /dev/null +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.scss @@ -0,0 +1,164 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: row; + height: 100%; + font-family: Inter, serif; + font-style: normal; +} + +form { + width: 100%; + + .guardian-input-container { + margin-bottom: 24px; + } + + .guardian-textarea-container { + margin-bottom: 24px; + } +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} + +.ddl { + width: 210px; +} + +.field { + display: flex; + flex-direction: column; + gap: 5px; + width: 210px; + + input { + height: 40px; + border-radius: 8px; + font-family: Inter; + font-weight: 400; + font-size: 14px !important; + color: #23252E; + line-height: 16px; + padding: 12px 16px; + } +} + +.fields-grid { + display:grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap:16px; + align-items:end; +} +.delete-field { + grid-column: 5; + justify-self:end; + position: relative; + top: -4px; +} +.label-field{ grid-column: 1; } +.description-field{ + grid-column: 2 / 5; + min-width: 0; + width: 100%; +} + +.required-field{ + grid-column: 1 / 5; + display:flex; + align-items:center; + gap:10px; + padding-bottom:10px; + margin-top: 8px; + font-family: Inter; + font-weight: 500; + font-size: 14px; + line-height: 24px; +} + +.group-container { + background-color: #F9FAFC; + border-radius: 8px; + border: 1px solid #E1E7EF; + opacity: 1; + gap: 16px; + padding: 16px; + margin-bottom: 24px; +} + +.guardian-icon-button { + width: 24px; + height: 24px; +} + +.add-button, +.dialog-button button { + height: 40px; + padding: 5px 24px; +} + +.add-button { + margin-bottom: 24px; +} + +.parameter-label { + font-weight: 500; + font-size: 12px; + line-height: 14px; +} + +::ng-deep .parameter-dropdown { + .p-dropdown, + .p-multiselect { + border-radius: 8px !important; + height: 40px; + + .custom { + position: inherit; + line-height: 16px; + overflow: visible; + } + } + + .p-dropdown-label, + .p-multiselect-label { + height: 40px; + font-family: Inter; + font-weight: 400; + font-size: 14px; + padding: 12px 16px; + color: #23252E; + line-height: 16px; + } + + .guardian-dropdown .p-dropdown .p-inputtext { + padding: 12px 16px; + color: #23252E; + } + +} + +input.ng-invalid.ng-touched, +::ng-deep .parameter-dropdown.ng-invalid.ng-touched .p-dropdown, +::ng-deep .parameter-dropdown.ng-invalid.ng-touched .p-multiselect { + border-color: #f44336; +} + +::ng-deep .is-invalid .guardian-dropdown .p-dropdown { + border-color: #f44336; +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.ts b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.ts new file mode 100644 index 0000000000..2e6cf65cb7 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component.ts @@ -0,0 +1,241 @@ +import { Component, OnInit } from '@angular/core'; +import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { PolicyBlock, PolicyTemplate } from '../../structures'; +import { RegisteredService } from '../../services/registered.service'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { Subject, takeUntil } from 'rxjs'; +import { PolicyEditableField, PolicyEditableFieldDTO } from '@guardian/interfaces'; + +type PolicyEditableFieldForm = { + blockTag: FormControl; + property: FormControl; + visible: FormControl; + applyTo: FormControl; + required: FormControl; + label: FormControl; + shortDescription: FormControl; +}; + +type PolicyForm = { + fields: FormArray>; +}; + +@Component({ + selector: 'app-parameters-config-dialog', + templateUrl: './policy-parameters-config-dialog.component.html', + styleUrls: ['./policy-parameters-config-dialog.component.scss'], +}) +export class PolicyParametersConfigDialog implements OnInit { + loading = true; + submitted = false; + form: FormGroup; + policyTemplate: PolicyTemplate; + policyEditableFields: PolicyEditableField[] = []; + filteredBlocks: Map = new Map(); + currentBlocks: PolicyBlock[] = []; + additionalOptionsApplyTo = [ + { _name: 'All' }, + { _name: 'Self' } + ]; + + private _destroy$ = new Subject(); + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private fb: FormBuilder, + private registeredService: RegisteredService, + private policyEngineService: PolicyEngineService, + ) { + this.policyTemplate = this.config.data.policy; + + this.form = this.fb.group({ + fields: this.fb.array(this.policyEditableFields.map(m => this.createFieldGroup(m))), + }); + } + + ngOnInit() { + this.loading = false; + + if(this.policyTemplate.editableParametersSettings) { + this.policyEditableFields = this.policyTemplate.editableParametersSettings?.map(ep => PolicyEditableFieldDTO.fromDTO(ep)); + } + + this.policyEngineService.getBlockInformation() + .pipe(takeUntil(this._destroy$)) + .subscribe(blockInfo => { + this.registeredService.registerConfig(blockInfo); + this.filterBlocks(); + this.loadData(); + }); + } + + filterBlocks(): void { + this.currentBlocks = []; + this.policyTemplate.allBlocks.forEach(block => { + const props = this.registeredService.getCustomProperties(block.blockType); + if(props && props.length > 0) { + const propsWithPath = this.setPath(props.filter((prop: any) => prop.editable), []); + if(propsWithPath && propsWithPath.length > 0) { + this.filteredBlocks.set(block.tag, propsWithPath); + this.currentBlocks.push(block); + } + } + }); + } + + loadData() { + const fields = this.policyTemplate.editableParametersSettings; + if(!fields?.length) { + return; + } + + fields.forEach((field: PolicyEditableFieldDTO) => { + if(!this.filteredBlocks.has(field.blockTag)) { + return; + } + + const formField: any = { ...field }; + const g = this.createFieldGroup(formField); + this.fields.push(g); + }); + } + + trackByIndex = (i: number) => i; + + get fields(): FormArray { + return this.form.get('fields') as FormArray; + } + + get applyToOptions(): any[] { + return [...this.additionalOptionsApplyTo, ...this.policyTemplate.policyRoles]; + } + + get fieldGroups(): FormGroup[] { + return this.form.controls.fields.controls as FormGroup[]; + } + + public propertiesOptions(index: number): any[] { + return this.policyEditableFields[index]?.properties ?? []; + } + + private propertyInCurrentBlock(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!control.parent) return null; + + const propertyValue = control.value; + if (!propertyValue) return null; + + const blockTag = control.parent.get('blockTag')?.value; + if (!blockTag) return null; + + const props = this.filteredBlocks.get(blockTag) ?? []; + + return props.some(p => p.path === propertyValue) + ? null + : { notInOptions: true }; + }; + } + + private setPath(properties:any[], result:any[], parent?:string) { + if(!properties) return; + + for(const prop of properties) { + prop.path = parent ? parent + '.' + prop.name : prop.name; + if(prop.properties) { + this.setPath(prop.properties, result, prop.path); + } + else { + result.push(prop); + } + } + return result; + } + + onBlockChange(selected: any, index: number): void { + const fg = this.fields.at(index) as FormGroup; + fg.controls.blockTag.setValue(selected); + + this.policyEditableFields[index].properties = this.filteredBlocks.get(selected) ?? []; + + fg.controls.blockTag.markAsDirty(); + fg.controls.blockTag.markAsTouched(); + fg.controls.property.updateValueAndValidity({ emitEvent: false }); + } + + createFieldGroup(m?: Partial): FormGroup { + return this.fb.group({ + blockTag: this.fb.control(m?.blockTag ?? '', { nonNullable: true, validators: [Validators.required] }), + property: this.fb.control(m?.propertyPath ?? '', { nonNullable: true, validators: [Validators.required, this.propertyInCurrentBlock()] }), + visible: this.fb.control(m?.visible ?? [], { nonNullable: true, validators: [Validators.required] }), + applyTo: this.fb.control(m?.applyTo ?? [], { nonNullable: true, validators: [Validators.required] }), + label: this.fb.control(m?.label ?? '', { nonNullable: true, validators: [Validators.required] }), + required: this.fb.control(m?.required ?? false, { nonNullable: true }), + shortDescription: this.fb.control(m?.shortDescription ?? '', { nonNullable: true }), + }); + } + + addField(): void { + const g = this.createFieldGroup(); + this.fields.push(g); + this.policyEditableFields.push(new PolicyEditableField()); + } + + removeField(index: number): void { + this.fields.removeAt(index); + this.policyEditableFields.splice(index, 1); + } + + onClose(): void { + this._destroy$.next(); + this.ref.close(); + } + + buildFields(): PolicyEditableFieldDTO[] { + const blocksByTag = new Map( + this.policyTemplate.allBlocks.map(b => [b.tag, b.blockType] as const) + ); + + return this.form.getRawValue().fields ? + this.form.getRawValue().fields.map(val => { + const field = new PolicyEditableFieldDTO(); + field.blockType = blocksByTag.get(val.blockTag) ?? ''; + field.blockTag = val.blockTag; + field.propertyPath = val.property; + field.visible = val.visible; + field.applyTo = val.applyTo; + field.label = val.label; + field.required = val.required; + field.shortDescription = val.shortDescription; + return field; + }) + : []; + } + + async submit(): Promise { + this.submitted = true; + + if (this.form.invalid) { + this.form.markAllAsTouched(); + return; + } + + const fields = this.buildFields(); + + const root = this.policyTemplate.getJSON(); + root.editableParametersSettings = fields; + + if (root) { + this.loading = true; + this.policyEngineService.update(this.policyTemplate.id, root) + .pipe(takeUntil(this._destroy$)) + .subscribe((policy: any) => { + if (policy) { + this.ref.close(policy); + } + }); + } + } + +} diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.html b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.html new file mode 100644 index 0000000000..4c7d864ad1 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.html @@ -0,0 +1,49 @@ +
+
+
Policy Settings
+
+
+ +
+
+
+
+
+
+
+
+ + These settings will be used during policy execution. Please fill in all required fields where applicable. +
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.scss b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.scss new file mode 100644 index 0000000000..aab6107ebd --- /dev/null +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.scss @@ -0,0 +1,187 @@ +.context { + position: relative; + overflow-y: auto; + padding: 5px 3px 20px 3px; + display: flex; + flex-direction: column; + height: 100%; + + .filters { + display: flex; + flex-direction: column; + width: 100%; + + .filter-row { + height: 40px; + position: relative; + width: 100%; + display: flex; + flex-direction: row; + } + + .p-input-icon-right { + width: 100%; + } + + .search-policy-input { + width: 100%; + height: 40px; + border: 1px solid #e1e7ef; + border-radius: 8px; + padding-left: 16px; + position: relative; + + &:hover { + border-color: #2196F3; + } + + &:focus { + outline: 1px solid #2196F3; + } + } + + .guardian-button { + width: 150px; + min-width: 150px; + max-width: 150px; + margin-left: 12px; + padding: 6px 12px; + } + } + + .empty-grid { + margin-top: 16px; + display: flex; + flex-direction: column; + width: 100%; + overflow: auto; + height: 100%; + justify-content: center; + align-items: center; + color: #848FA9; + + .empty-grid-icon { + margin-bottom: 2px; + } + + .empty-grid-header { + font-weight: 500; + margin-bottom: 4px; + } + } + + .grid-container { + margin-top: 16px; + display: flex; + flex-direction: column; + width: 100%; + overflow: auto; + + .col-auto { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .col-110 { + width: 100px; + min-width: 100px; + max-width: 100px; + + &.center { + justify-content: center; + display: flex; + } + } + + .grid-header { + display: flex; + flex-direction: row; + width: 100%; + height: 46px; + min-height: 46px; + max-height: 46px; + + &>div { + font-weight: 500; + padding: 12px 16px; + } + } + + .grid-body { + display: flex; + flex-direction: column; + width: 100%; + border: 1px solid #E1E7EF; + border-radius: 6px; + overflow: auto; + + .grid-row { + display: flex; + flex-direction: row; + width: 100%; + height: 50px; + min-height: 50px; + max-height: 50px; + border-bottom: 1px solid #E1E7EF; + cursor: pointer; + + &>div { + padding: 13px 16px; + } + + &:hover { + background: #f1f1f1; + } + } + } + } + + .checkbox-input { + width: 16px; + height: 16px; + cursor: pointer; + } + + .import-icon { + height: 24px; + display: flex; + align-items: center; + text-transform: capitalize; + + svg-icon { + margin-right: 8px; + } + } +} + +.dialog-body { + max-height: 75vh; + overflow-y: auto; +} + +.dropdown-type { + color: #848FA9; + padding: 0px 8px 0px 0px; +} + +.settings-warning { + background-color: #F7F7F7; + border: 1px solid #C5C2C2; + padding: 12px; + display: flex; + margin-bottom: 24px; + justify-content: center; + border-radius: 8px; + + span { + font-weight: 500; + font-size: 12px; + margin-left: 8px; + } +} + +.dialog-button button { + padding: 5px 24px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.ts b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.ts new file mode 100644 index 0000000000..cd1310e1d8 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/dialogs/policy-parameters-dialog/policy-parameters-dialog.component.ts @@ -0,0 +1,165 @@ +import { Component } from '@angular/core'; +import { AbstractControl, FormArray, FormControl, FormGroup, UntypedFormControl, Validators } from '@angular/forms'; +import { PolicyEditableFieldDTO, UserPermissions } from '@guardian/interfaces'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { RegisteredService } from '../../services/registered.service'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { PolicyBlock } from '../../structures'; +import { Subject, takeUntil } from 'rxjs'; + +/** + * Policy parameters dialog. + */ +interface PolicyParameterItem { + block: PolicyBlock, + property: any, + propertyPath: string, + config: PolicyEditableFieldDTO, +} + +@Component({ + selector: 'policy-parameters-dialog', + templateUrl: './policy-parameters-dialog.component.html', + styleUrls: ['./policy-parameters-dialog.component.scss'] +}) +export class PolicyParametersDialog { + public blockInfo: any; + public loading = false; + public policyId: string; + public editableParameters: PolicyEditableFieldDTO[] = []; + + public user: UserPermissions = new UserPermissions(); + + public searchFilter = new UntypedFormControl(''); + public readonly: boolean = false; + private _destroy$ = new Subject(); + + public items: PolicyParameterItem[] = []; + + public form: FormGroup; + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private registeredService: RegisteredService, + private policyEngineService: PolicyEngineService + ) { + this.policyId = this.config.data?.policyId; + + this.form = new FormGroup({ + items: new FormArray>([]) + }); + } + + ngOnInit() { + this.policyEngineService.getBlockInformation() + .pipe(takeUntil(this._destroy$)) + .subscribe(blockInfo => { + this.registeredService.registerConfig(blockInfo); + this.blockInfo = blockInfo; + this.loadConfig(); + }); + } + + loadConfig() { + this.policyEngineService.getParametersConfig(this.policyId).subscribe((response: any) => { + this.editableParameters = response || []; + this.loadItems(); + }); + } + + loadItems() { + for(let i=0; i< this.editableParameters.length; i++) { + const field = this.editableParameters[i]; + const block = structuredClone(this.blockInfo[field.blockType]); + + const property = this.findByPath(block.properties, field.propertyPath); + if (!property) { + continue; + } + + this.items.push({ + block, + property, + propertyPath: field.propertyPath, + config: field + }); + + if (property.type === 'Array') { + const values = Array.isArray(field.value) ? field.value : []; + + const arr = new FormArray( + values.map(v => { + const g: Record = {}; + for (const p of property.items.properties ?? []) { + g[p.name] = new FormControl( + v?.[p.name] ?? null, + p.required ? [Validators.required] : [] + ); + } + return new FormGroup(g); + }) + ); + + this.form.addControl(field.propertyPath, arr); + } + else { + this.form.addControl( + field.propertyPath, + new FormControl( + field.value ?? null, + field.required ? [Validators.required] : [] + ) + ); + } + } + + setTimeout(() => { + Object.values(this.form.controls).forEach(ctrl => { + ctrl.markAsDirty(); + ctrl.markAsTouched(); + }); + }) + } + + findByPath(items: any[], path: string): any | undefined { + const parts = path.split('.'); + let currentLevel = items; + let found; + + for (const part of parts) { + found = currentLevel.find(p => p.name === part); + if (!found) return undefined; + + currentLevel = found.properties || []; + } + + return found; + } + + async onSubmit() { + for(let i = 0; i< this.editableParameters.length; i++) { + const field = this.editableParameters[i]; + const item = this.items.find(item => item.propertyPath === field.propertyPath); + if(item) { + field.value = this.form.controls[item.propertyPath]?.value;; + } + } + + this.policyEngineService.saveParameters( + this.policyId, + this.editableParameters + ).subscribe( + (_) => { + this.onClose(); + } + ); + } + + onSave() { + } + + public onClose(): void { + this.ref.close(null); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/parameter-document-path.component.html b/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/parameter-document-path.component.html new file mode 100644 index 0000000000..ac623b08b7 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/parameter-document-path.component.html @@ -0,0 +1,22 @@ +
+ + + + +
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/parameter-document-path.component.scss b/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/parameter-document-path.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/paramter-document-path.component.ts b/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/paramter-document-path.component.ts new file mode 100644 index 0000000000..2166a54392 --- /dev/null +++ b/frontend/src/app/modules/policy-engine/helpers/parameter-document-path/paramter-document-path.component.ts @@ -0,0 +1,125 @@ +import { + Component, + EventEmitter, + forwardRef, + Input, + Output, + SimpleChanges +} from '@angular/core'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR +} from '@angular/forms'; + +@Component({ + selector: 'app-paramter-document-path', + templateUrl: './parameter-document-path.component.html', + styleUrls: ['./parameter-document-path.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ParameterDocumentPath), + multi: true + } + ] +}) +export class ParameterDocumentPath implements ControlValueAccessor { + @Input() displayTooltip!: boolean; + @Input() readonly!: boolean; + + @Output() valueChange = new EventEmitter(); + @Output() change = new EventEmitter(); + + value: string = ''; + startPath: string = ''; + endPath: string = ''; + disabled = false; + pathOptions = [ + { label: 'Root', value: '', title: ' ' }, + { label: 'Document', value: 'document.', title: 'document.' }, + { label: 'Credential Subjects', value: 'document.credentialSubject.', title: 'document.credentialSubject.' }, + { label: 'First Credential Subjects', value: 'document.credentialSubject.0.', title: 'document.credentialSubject.0.' }, + { label: 'Last Credential Subjects', value: 'document.credentialSubject.L.', title: 'document.credentialSubject.L.' }, + { label: 'Verifiable Credentials', value: 'document.verifiableCredential.', title: 'document.verifiableCredential.' }, + { label: 'First Verifiable Credential', value: 'document.verifiableCredential.0.', title: 'document.verifiableCredential.0.' }, + { label: 'Last Verifiable Credential', value: 'document.verifiableCredential.L.', title: 'document.verifiableCredential.L.' }, + { label: 'Attributes', value: 'option.', title: 'option.' } + ]; + map = [ + { value: '', name: 'Root' }, + { value: 'document.', name: 'Document' }, + { value: 'document.credentialSubject.', name: 'Credential Subjects' }, + { value: 'document.credentialSubject.0.', name: 'First Credential Subjects' }, + { value: 'document.credentialSubject.L.', name: 'Last Credential Subjects' }, + { value: 'document.verifiableCredential.', name: 'Verifiable Credentials' }, + { value: 'document.verifiableCredential.0.', name: 'First Verifiable Credential' }, + { value: 'document.verifiableCredential.L.', name: 'Last Verifiable Credential' }, + { value: 'option.', name: 'Attributes' } + ]; + + private onTouched: () => void = () => {}; + private onChangeFn: (value: string) => void = () => {}; + + constructor() {} + + writeValue(value: string | null): void { + this.value = value ?? ''; + this.applyValueToParts(this.value); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChangeFn = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['readonly']) { + return; + } + + this.applyValueToParts(this.value); + } + + onInternalChange(): void { + const newValue = `${this.startPath ?? ''}${this.endPath ?? ''}`; + this.value = newValue; + + this.onChangeFn(this.value); + this.valueChange.emit(this.value); + this.change.emit(this.value); + } + + onInput(): void { + this.onInternalChange(); + } + + onBlur(): void { + this.onTouched(); + } + + private applyValueToParts(value: string): void { + if (!value) { + this.startPath = ''; + this.endPath = ''; + return; + } + + let matched = ''; + + for (const item of this.map) { + if (value.startsWith(item.value) && item.value.length >= matched.length) { + matched = item.value; + } + } + + this.startPath = matched; + this.endPath = value.substring(matched.length); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/helpers/select-block/select-block.component.scss b/frontend/src/app/modules/policy-engine/helpers/select-block/select-block.component.scss index 2842126ad4..28786fe0c3 100644 --- a/frontend/src/app/modules/policy-engine/helpers/select-block/select-block.component.scss +++ b/frontend/src/app/modules/policy-engine/helpers/select-block/select-block.component.scss @@ -64,7 +64,7 @@ } .guardian-dropdown::ng-deep .p-dropdown { - border-radius: 0px !important; + border-radius: 0px; } .guardian-multiselect::ng-deep .p-multiselect { diff --git a/frontend/src/app/modules/policy-engine/policies/policies.component.ts b/frontend/src/app/modules/policy-engine/policies/policies.component.ts index a1391d175c..7dfb67e48a 100644 --- a/frontend/src/app/modules/policy-engine/policies/policies.component.ts +++ b/frontend/src/app/modules/policy-engine/policies/policies.component.ts @@ -53,6 +53,7 @@ import { IndexedDbRegistryService } from 'src/app/services/indexed-db-registry.s import { DB_NAME, STORES_NAME } from 'src/app/constants'; import { ToastrService } from 'ngx-toastr'; import { UserPolicyDialog } from '../dialogs/user-policy-dialog/user-policy-dialog.component'; +import { PolicyParametersDialog } from '../dialogs/policy-parameters-dialog/policy-parameters-dialog.component'; import { CustomConfirmDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; import { ExternalPoliciesService } from 'src/app/services/external-policy.service'; @@ -629,7 +630,24 @@ export class PoliciesComponent implements OnInit { click: () => this.userPolicyManage(policy) }) ] - }, { + }, + // { + // tooltip: 'Parameters', + // group: false, + // visible: PolicyHelper.isPublishMode(policy) && this.user.POLICIES_POLICY_MANAGE, + // color: 'primary-color', + // buttons: [ + // new MenuButton({ + // visible: true, + // disabled: false, + // tooltip: 'Policy parameters', + // icon: 'settings', + // color: 'primary-color', + // click: () => this.policyParameters(policy) + // }) + // ] + // }, + { tooltip: 'Delete', group: false, visible: true, @@ -1955,6 +1973,19 @@ export class PoliciesComponent implements OnInit { dialogRef.onClose.pipe(takeUntil(this._destroy$)).subscribe(async (options) => { }); } + public policyParameters(policy?: any) { + this.policySubMenu?.hide(); + const dialogRef = this.dialogService.open(PolicyParametersDialog, { + showHeader: false, + width: '90%', + styleClass: 'guardian-dialog', + data: { + policyId: policy?.id + }, + }); + dialogRef.onClose.pipe(takeUntil(this._destroy$)).subscribe(async (options) => { }); + } + public onSelectAllItems(event: any) { if (event.checked) { this.selectedItems = [...this.selectedItems, ...this.policiesList.filter((item: any) => (item.status === PolicyStatus.DRAFT || @@ -2105,9 +2136,9 @@ export class PoliciesComponent implements OnInit { this.policyEngineService .disconnect(policy.id) .pipe(takeUntil(this._destroy$)) - .subscribe((result) => { + .subscribe((result: any) => { this.loadAllPolicy(); - }, (e) => { + }, (e: any) => { this.loading = false; }); } @@ -2185,7 +2216,7 @@ export class PoliciesComponent implements OnInit { this.policyEngineService .reconnect(policy.id) .pipe(takeUntil(this._destroy$)) - .subscribe((result) => { + .subscribe((result: any) => { this.loadAllPolicy(); }, (e) => { this.loading = false; diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.html b/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.html index 157cdfcaac..e74fcbe278 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.html +++ b/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.html @@ -101,6 +101,7 @@ + {{ property.text }} diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.ts b/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.ts index 4c0e7e7645..2c9e238ee3 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-configuration/common-property/common-property.component.ts @@ -3,6 +3,7 @@ import { RegisteredService } from '../../services/registered.service'; import { PolicyBlock, RoleVariables, SchemaVariables, } from '../../structures'; import { DialogService } from 'primeng/dynamicdialog'; import { CodeEditorDialogComponent } from '../../dialogs/code-editor-dialog/code-editor-dialog.component'; +import { CustomConfirmDialogComponent } from 'src/app/modules/common/custom-confirm-dialog/custom-confirm-dialog.component'; /** * common property @@ -47,6 +48,7 @@ export class CommonPropertyComponent implements OnInit { loaded: boolean = false; schemas!: SchemaVariables[]; roles!: RoleVariables[]; + lastValue: any; constructor( private registeredService: RegisteredService, @@ -121,15 +123,47 @@ export class CommonPropertyComponent implements OnInit { } } } + + this.lastValue = this.value; } customPropCollapse(property: any) { return this.collapse; } + openConfirmationDialog(config: any) { + const dialogRef = this.dialog.open(CustomConfirmDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: config.title, + text: config.description, + buttons: [ + { name: 'No', class: 'secondary' }, + { name: 'Yes', class: 'primary' } + ] + } + }); + + dialogRef.onClose.subscribe(result => { + if (result !== 'Yes') { + this.value = this.lastValue; + } + this.lastValue = this.value; + this.needUpdate = true; + this.update.emit(); + }); + } + onSave() { - this.needUpdate = true; - this.update.emit(); + if (this.property.confirmation && this.property.confirmation.condition == this.value) { + this.openConfirmationDialog(this.property.confirmation); + } else { + this.lastValue = this.value; + this.needUpdate = true; + this.update.emit(); + } } editCode($event: MouseEvent) { diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.html b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.html index 70c08832c5..39025b49eb 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.html +++ b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.html @@ -332,6 +332,11 @@
+ +
+ + Parameters +
{ + if(this.policyTemplate && policy) { + this.policyTemplate.editableParametersSettings = policy?.editableParametersSettings; + } + }); + } + public onSchemas() { switch (this.rootType) { case 'Policy': { diff --git a/frontend/src/app/modules/policy-engine/policy-engine.module.ts b/frontend/src/app/modules/policy-engine/policy-engine.module.ts index 2f8a9752ea..9c8e6916c4 100644 --- a/frontend/src/app/modules/policy-engine/policy-engine.module.ts +++ b/frontend/src/app/modules/policy-engine/policy-engine.module.ts @@ -177,6 +177,10 @@ import { FieldLinkDialog } from './dialogs/field-link-dialog/field-link-dialog.c import { ChangeBlockSettingsDialog } from './dialogs/change-block-settings-dialog/change-block-settings-dialog.component'; import { ApproveUpdateVcDocumentDialogComponent } from './dialogs/approve-update-vc-document-dialog/approve-update-vc-document-dialog.component' import { AddDocumentDialog } from './dialogs/add-document-dialog/add-document-dialog.component'; +import { PolicyParametersDialog } from './dialogs/policy-parameters-dialog/policy-parameters-dialog.component'; +import { PolicyParameterPropertyComponent } from 'src/app/components/policy-parameter-property/policy-parameter-property.component'; +import { PolicyParametersConfigDialog } from './dialogs/policy-parameters-config-dialog/policy-parameters-config-dialog.component'; +import { ParameterDocumentPath } from './helpers/parameter-document-path/paramter-document-path.component'; @NgModule({ declarations: [ @@ -310,7 +314,11 @@ import { AddDocumentDialog } from './dialogs/add-document-dialog/add-document-di FieldLinkDialog, AddDocumentDialog, ChangeBlockSettingsDialog, - ApproveUpdateVcDocumentDialogComponent + ApproveUpdateVcDocumentDialogComponent, + PolicyParametersConfigDialog, + PolicyParametersDialog, + PolicyParameterPropertyComponent, + ParameterDocumentPath ], imports: [ CommonModule, diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html index c1fd1ea93c..e95d207980 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html +++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.html @@ -261,6 +261,12 @@ > Role: {{ policyInfo.userRoles.join(", ") }} + = new Subject(); public activeTabIndex = 0; public disconnected: boolean = false; + public editableParameters: any[] = []; public currentSavepoint: any = null; private restoreDialogOpened: boolean = false; @@ -264,7 +266,7 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { } else { this.loadPolicyById(this.policyId); } - }, (e) => { + }, (e: any) => { this.loading = false; }); } @@ -277,7 +279,8 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { this.policyEngineService.policy(policyId), this.policyEngineService.policyBlock(policyId, null), this.policyEngineService.getGroups(policyId, this.savepointIds), - this.externalPoliciesService.getActionRequestsCount({ policyId }) + this.externalPoliciesService.getActionRequestsCount({ policyId }), + this.policyEngineService.getParametersConfig(policyId), ])) ).subscribe( (value) => { @@ -285,6 +288,7 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { this.policy = value[1]; this.groups = value[2] || []; const count: any = value[3]?.body || {}; + this.editableParameters = value[4]; this.virtualUsers = []; this.isMultipleGroups = !!(this.policyInfo?.policyGroups && this.groups?.length); @@ -323,6 +327,10 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { this.newRequestsExist = count.requestsCount > 0; this.newActionsExist = count.actionsCount > 0 || count.delayCount > 0; + + if (this.editableParameters?.length && this.editableParameters.some((p: any) => p.required && !p.value)) { + this.openParametersSettings(); + } }, (e) => { this.loading = false; }); @@ -479,6 +487,19 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { dialogRef.onClose.subscribe(async (result) => { }); } + public openParametersSettings() { + const dialogRef = this.dialogService.open(PolicyParametersDialog, { + showHeader: false, + width: '90%', + styleClass: 'guardian-dialog', + data: { + policyId: this.policyId + }, + }); + + dialogRef.onClose.subscribe(async (result) => { }); + } + public onPage(event: any): void { if (this.pageSize != event.pageSize) { this.pageIndex = 0; @@ -877,9 +898,9 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { this.loading = true; this.policyEngineService .reconnect(this.policyId) - .subscribe((result) => { + .subscribe((result: any) => { this.checkPolicyStatus(this.policyId); - }, (e) => { + }, (e: any) => { this.loading = false; }); } diff --git a/frontend/src/app/modules/policy-engine/services/blocks-information.ts b/frontend/src/app/modules/policy-engine/services/blocks-information.ts index cac7456c49..2402c966fa 100644 --- a/frontend/src/app/modules/policy-engine/services/blocks-information.ts +++ b/frontend/src/app/modules/policy-engine/services/blocks-information.ts @@ -68,7 +68,7 @@ const Container: IBlockSetting = { group: BlockGroup.Main, header: BlockHeaders.UIComponents, factory: ContainerBlockComponent, - property: ContainerConfigComponent, + property: null, code: null, allowedChildren: [ { type: BlockType.Information }, @@ -182,7 +182,7 @@ const GroupManagerBlock: IBlockSetting = { group: BlockGroup.Main, header: BlockHeaders.UIComponents, factory: GroupManagerBlockComponent, - property: GroupManagerConfigComponent, + property: null, code: null, } @@ -192,7 +192,7 @@ const container: IBlockSetting = { group: BlockGroup.Main, header: BlockHeaders.UIComponents, factory: InformationBlockComponent, - property: InformationConfigComponent, + property: null, code: null } @@ -314,7 +314,7 @@ const Switch: IBlockSetting = { group: BlockGroup.Main, header: BlockHeaders.ServerBlocks, factory: null, - property: SwitchConfigComponent, + property: null, code: null, about: { output: (value: any, block: PolicyBlock) => { @@ -457,7 +457,7 @@ const ExternalData: IBlockSetting = { group: BlockGroup.Documents, header: BlockHeaders.ServerBlocks, factory: null, - property: ExternalDataConfigComponent, + property: null, code: null, allowedChildren: [{ type: BlockType.DocumentValidatorBlock, @@ -545,7 +545,7 @@ const ReassigningBlock: IBlockSetting = { group: BlockGroup.Documents, header: BlockHeaders.ServerBlocks, factory: null, - property: ReassigningConfigComponent, + property: null, code: null, } @@ -613,7 +613,7 @@ const DocumentsSourceAddon: IBlockSetting = { group: BlockGroup.Documents, header: BlockHeaders.Addons, factory: null, - property: SourceAddonConfigComponent, + property: null, code: null, allowedChildren: [{ type: BlockType.FiltersAddon, @@ -640,7 +640,7 @@ const DataTransformationAddon: IBlockSetting = { group: BlockGroup.UnGrouped, header: BlockHeaders.Addons, factory: null, - property: DataTransformationConfigComponent, + property: null, code: null, } @@ -680,7 +680,7 @@ const DocumentValidatorBlock: IBlockSetting = { group: BlockGroup.Documents, header: BlockHeaders.Addons, factory: null, - property: DocumentValidatorConfigComponent, + property: null, code: null } @@ -801,7 +801,7 @@ const CalculateMathAddon: IBlockSetting = { group: BlockGroup.Calculate, header: BlockHeaders.Addons, factory: null, - property: CalculateMathConfigComponent, + property: null, code: null, } diff --git a/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts b/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts index df89368ac6..122b6399e7 100644 --- a/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts +++ b/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts @@ -1,4 +1,4 @@ -import { BlockType, GenerateUUIDv4, GroupRelationshipType, PolicyStatus, Schema, Token, } from '@guardian/interfaces'; +import { BlockType, GenerateUUIDv4, GroupRelationshipType, PolicyEditableFieldDTO, PolicyStatus, Schema, Token, } from '@guardian/interfaces'; import { PolicyRole } from './policy-role.model'; import { PolicyGroup } from './policy-group.model'; import { PolicyToken } from './policy-token.model'; @@ -38,6 +38,7 @@ export class PolicyTemplate { public readonly previousVersion!: string; public readonly tests!: any; + private _editableParametersSettings?: PolicyEditableFieldDTO[]; private _policyTag!: string; private _name!: string; private _description!: string; @@ -99,6 +100,7 @@ export class PolicyTemplate { this.version = policy.version; this.previousVersion = policy.previousVersion; this.tests = policy.tests; + this.editableParametersSettings = policy.editableParametersSettings; this.buildPolicy(policy); this.buildBlock(policy.config); @@ -120,6 +122,15 @@ export class PolicyTemplate { this.isTest = this.isDraft || this.isDryRun; } + public get editableParametersSettings(): PolicyEditableFieldDTO[] | undefined { + return this._editableParametersSettings; + } + + public set editableParametersSettings(value: PolicyEditableFieldDTO[] | undefined) { + this._editableParametersSettings = value; + } + + public get policyTag(): string { return this._policyTag; } @@ -648,6 +659,7 @@ export class PolicyTemplate { policyTokens: Array(), policyGroups: Array(), config: null, + editableParametersSettings: this.editableParametersSettings }; for (const role of this.policyRoles) { json.policyRoles.push(role.getJSON()); diff --git a/frontend/src/app/services/policy-engine.service.ts b/frontend/src/app/services/policy-engine.service.ts index 9f31eca8ff..d92e721224 100644 --- a/frontend/src/app/services/policy-engine.service.ts +++ b/frontend/src/app/services/policy-engine.service.ts @@ -675,6 +675,14 @@ export class PolicyEngineService { return this.http.get(`${this.url}/${policyId}/get-all-version-vc-documents/${documentId}`); } + public saveParameters(policyId: string, data: any): Observable { + return this.http.post(`${this.url}/${policyId}/parameters/`, data); + } + + public getParametersConfig(policyId: string): Observable { + return this.http.get(`${this.url}/${policyId}/parameters/config`); + } + public disconnect(policyId: string): Observable { return this.http.put(`${this.url}/${policyId}/disconnect`, null); } diff --git a/frontend/src/assets/images/icons/settings.svg b/frontend/src/assets/images/icons/settings.svg new file mode 100644 index 0000000000..f6e22409cf --- /dev/null +++ b/frontend/src/assets/images/icons/settings.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/guardian-service/src/policy-engine/policy-engine.service.ts b/guardian-service/src/policy-engine/policy-engine.service.ts index e5bd411034..7049a8dbdd 100644 --- a/guardian-service/src/policy-engine/policy-engine.service.ts +++ b/guardian-service/src/policy-engine/policy-engine.service.ts @@ -30,6 +30,7 @@ import { PolicyDiscussion, PolicyImportExport, PolicyMessage, + PolicyParameters, RecordImportExport, RunFunctionAsync, Schema as SchemaCollection, @@ -59,7 +60,9 @@ import { PolicyActionStatus, IgnoreRule, SchemaStatus, - MigrationConfig, MigrationRunStatus + PolicyEditableFieldDTO, + MigrationConfig, + MigrationRunStatus } from '@guardian/interfaces'; import { AccountId, PrivateKey } from '@hiero-ledger/sdk'; import { NatsConnection } from 'nats'; @@ -4990,6 +4993,82 @@ export class PolicyEngineService { return new MessageError(error); } }) + + this.channel.getMessages(PolicyEngineEvents.SAVE_POLICY_PARAMETERS_VALUES, + async (msg: { owner: IOwner, userDID: string, policyId: string, config: PolicyEditableFieldDTO[] }) => { + try { + let result; + const { userDID, policyId, config } = msg; + + const found = await DatabaseServer.getPolicyParameters(userDID, policyId); + if(found) { + found.config = config; + result = await DatabaseServer.updatePolicyParameters(found); + } else { + const parameters = new PolicyParameters(); + parameters.userDID = userDID; + parameters.policyId = policyId; + parameters.config = config; + parameters.properties = {}; + parameters.updated = false; + + result = await DatabaseServer.createPolicyParameters(parameters); + } + + const allPolicyParameters = await DatabaseServer.getPolicyParametersByPolicyId(policyId); + allPolicyParameters.forEach(parameter => parameter.updated = true); + await DatabaseServer.setPolicyParametersUpdated(allPolicyParameters); + + return new MessageResponse(result); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE'], msg?.owner.id); + return new MessageError(error); + } + }) + + this.channel.getMessages(PolicyEngineEvents.GET_POLICY_PARAMETERS_VALUES, + async (msg: { owner: IOwner, user: IAuthUser, policyId: string }) => { + try { + const { user, policyId } = msg; + let result; + const parameters = await DatabaseServer.getPolicyParameters(user.did, policyId); + if(parameters && parameters.config?.length) { + result = parameters.config; + } + else { + const foundPolicy = await DatabaseServer.getPolicyById(policyId); + result = foundPolicy?.editableParametersSettings; + } + + const policy = await DatabaseServer.getPolicyById(policyId); + const userRole = await PolicyComponentsUtils.GetUserRole(policy, user); + + result = result.filter((item: PolicyEditableFieldDTO) => this.hasFieldPermission(item, userRole)); + + return new MessageResponse(result); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE'], msg?.owner.id); + return new MessageError(error); + } + }) //#endregion } + + public async hasFieldPermission(field: PolicyEditableFieldDTO, userRole: string): Promise { + if(field) { + if (field.visible.includes('ANY_ROLE')) { + return true; + } + if (field.visible.indexOf(userRole) > -1) { + return true; + } + if (field.visible.includes('NO_ROLE') && !userRole && userRole !== 'Administrator') { + return true; + } + if (field.visible.includes('OWNER') && userRole === 'Administrator') { + return true; + } + } + return false; + } } diff --git a/guardian-service/src/policy-engine/policy-engine.ts b/guardian-service/src/policy-engine/policy-engine.ts index 2232a79b7f..ff5775770d 100644 --- a/guardian-service/src/policy-engine/policy-engine.ts +++ b/guardian-service/src/policy-engine/policy-engine.ts @@ -1489,6 +1489,8 @@ export class PolicyEngine extends NatsService { if (model.originalHash) { const policyComponents = await PolicyImportExport.loadPolicyComponents(model); currentHash = PolicyImportExport.getPolicyHash(policyComponents); + console.log('original hash:', model.originalHash); + console.log('current hash:', currentHash); } const message = new PolicyMessage(MessageType.InstancePolicy, MessageAction.PublishPolicy); diff --git a/interfaces/src/helpers/index.ts b/interfaces/src/helpers/index.ts index 864db031b6..4abc364b60 100644 --- a/interfaces/src/helpers/index.ts +++ b/interfaces/src/helpers/index.ts @@ -9,4 +9,5 @@ export * from './generate-document.js'; export * from './sentinel-hub/index.js'; export * from './permissions-helper.js'; export * from './policy-helper.js'; -export * from './schema-json.js'; \ No newline at end of file +export * from './schema-json.js'; +export * from './policy-editable-field.js'; diff --git a/interfaces/src/helpers/policy-editable-field.ts b/interfaces/src/helpers/policy-editable-field.ts new file mode 100644 index 0000000000..67874a0191 --- /dev/null +++ b/interfaces/src/helpers/policy-editable-field.ts @@ -0,0 +1,45 @@ +export class PolicyEditableFieldDTO { + public blockType: string; + public blockTag: string; + public propertyPath: string; + public visible: string[]; + public applyTo: string[]; + public label: string; + public required: boolean; + public shortDescription: string; + + public value?: any; + + public static fromDTO(dto: PolicyEditableFieldDTO): PolicyEditableField { + return Object.assign(new PolicyEditableField(), dto); + } +} + +export class PolicyEditableField { + public blockType: string = ''; + public blockTag: string = ''; + public propertyPath: string = ''; + public visible: string[] = []; + public applyTo: string[] = []; + public required: boolean = false; + public blocks: any[] = []; + public properties: any[] = []; + public roles: any[] = []; + public targets: any[] = []; + public label: string = ''; + public shortDescription: string = ''; + + toDTO(): PolicyEditableFieldDTO { + const dto = new PolicyEditableFieldDTO(); + dto.blockType = this.blockType; + dto.blockTag = this.blockTag; + dto.propertyPath = this.propertyPath; + dto.visible = this.visible; + dto.applyTo = this.applyTo; + dto.label = this.label; + dto.required = this.required; + dto.shortDescription = this.shortDescription; + + return dto; + } +} \ No newline at end of file diff --git a/interfaces/src/type/messages/policy-engine-events.ts b/interfaces/src/type/messages/policy-engine-events.ts index 6ab43f2f2d..94405d8010 100644 --- a/interfaces/src/type/messages/policy-engine-events.ts +++ b/interfaces/src/type/messages/policy-engine-events.ts @@ -116,5 +116,9 @@ export enum PolicyEngineEvents { GET_POLICY_REPOSITORY_DOCUMENTS = 'policy-engine-policy-repository-documents', GET_POLICY_REPOSITORY_SCHEMAS = 'policy-engine-policy-repository-schemas', CREATE_NEW_VERSION_VC_DOCUMENT = 'policy-engine-create-new-version-vc-document', - GET_All_NEW_VERSION_VC_DOCUMENTS = 'policy-engine-get-all-new-version-vc-documents' + GET_All_NEW_VERSION_VC_DOCUMENTS = 'policy-engine-get-all-new-version-vc-documents', + + SAVE_POLICY_PARAMETERS_VALUES = 'policy-engine-save-policy-parameters-values', + GET_POLICY_PARAMETERS_VALUES = 'policy-engine-get-policy-parameters-values' + } diff --git a/policy-service/src/policy-engine/blocks/action-block.ts b/policy-service/src/policy-engine/blocks/action-block.ts index f45671dc39..4941b6e5b1 100644 --- a/policy-service/src/policy-engine/blocks/action-block.ts +++ b/policy-service/src/policy-engine/blocks/action-block.ts @@ -46,6 +46,8 @@ export class InterfaceDocumentActionBlock { async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -54,30 +56,30 @@ export class InterfaceDocumentActionBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - type: ref.options.type, - uiMetaData: ref.options.uiMetaData, - user: ref.options.user + type: options.type, + uiMetaData: options.uiMetaData, + user: options.user } - if (ref.options.type === 'selector') { - data.field = ref.options.field; + if (options.type === 'selector') { + data.field = options.field; } - if (ref.options.type === 'dropdown') { + if (options.type === 'dropdown') { let documents: any[] = await ref.getSources(user, null); documents = documents.filter((e) => !e.disconnected); - data.name = ref.options.name; - data.value = ref.options.value; - data.field = ref.options.field; + data.name = options.name; + data.value = options.value; + data.field = options.field; data.options = documents.map((e) => { return { - name: findOptions(e, ref.options.name), - value: findOptions(e, ref.options.value), + name: findOptions(e, options.name), + value: findOptions(e, options.value), } }); } - if (ref.options.type === 'transformation') { + if (options.type === 'transformation') { const children = []; for (const child of (ref.children as any[])) { if (child.blockClassName === 'UIAddon') { @@ -104,11 +106,13 @@ export class InterfaceDocumentActionBlock { async setData(user: PolicyUser, document: IPolicyDocument, _, actionStatus): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + const state: IPolicyEventState = { data: document }; let result: any = null; - if (ref.options.type === 'selector') { - const option = this.findOptions(document, ref.options.field, ref.options.uiMetaData.options); + if (options.type === 'selector') { + const option = this.findOptions(document, options.field, options.uiMetaData.options); if (option) { const newUser = option.user === UserType.CURRENT ? user @@ -118,23 +122,23 @@ export class InterfaceDocumentActionBlock { } } - if (ref.options.type === 'dropdown') { + if (options.type === 'dropdown') { const newUser = await PolicyUtils.getDocumentOwner(ref, document, user.userId); await ref.triggerEvents(PolicyOutputEventType.DropdownEvent, newUser, state, actionStatus); await ref.triggerEvents(PolicyOutputEventType.RefreshEvent, newUser, state, actionStatus); } - if (ref.options.type === 'download') { + if (options.type === 'download') { const sensorDid = document.document.credentialSubject[0].id; const userDID = document.owner; - const schemaObject = await PolicyUtils.loadSchemaByID(ref, ref.options.schema); + const schemaObject = await PolicyUtils.loadSchemaByID(ref, options.schema); const schema = new Schema(schemaObject); const sensorKey = await PolicyUtils.getAccountKey(ref, userDID, KeyType.KEY, sensorDid, user.userId); const key = await PolicyActionsUtils.downloadPrivateDocument(ref, userDID, sensorDid, user.userId); result = { - fileName: ref.options.filename || `${sensorDid}.config.json`, + fileName: options.filename || `${sensorDid}.config.json`, body: { - 'url': ref.options.targetUrl || process.env.MRV_ADDRESS, + 'url': options.targetUrl || process.env.MRV_ADDRESS, 'topic': ref.policyInstance?.topicId, 'hederaAccountId': key.hederaAccountId, 'hederaAccountKey': key.hederaAccountKey, @@ -155,13 +159,13 @@ export class InterfaceDocumentActionBlock { } } - if (ref.options.type === 'transformation') { + if (options.type === 'transformation') { // actionStatus.saveResult(state); await ref.triggerEvents(PolicyOutputEventType.RunEvent, user, state, actionStatus); } PolicyComponentsUtils.ExternalEventFn(new ExternalEvent(ExternalEventType.Set, ref, user, { - action: ref.options.type, + action: options.type, documents: ExternalDocuments(document) })); ref.backup(); diff --git a/policy-service/src/policy-engine/blocks/aggregate-block.ts b/policy-service/src/policy-engine/blocks/aggregate-block.ts index 0c906d44eb..8d661287a8 100644 --- a/policy-service/src/policy-engine/blocks/aggregate-block.ts +++ b/policy-service/src/policy-engine/blocks/aggregate-block.ts @@ -40,12 +40,14 @@ import { RecordActionStep } from '../record-action-step.js'; label: 'Disable user grouping', title: 'Disable user grouping', type: PropertyType.Checkbox, - default: false + default: false, + editable: true }, { name: 'groupByFields', label: 'Group By Fields', title: 'Group By Fields', type: PropertyType.Array, + editable: false, items: { label: 'Field Path', value: '@fieldPath', @@ -53,7 +55,8 @@ import { RecordActionStep } from '../record-action-step.js'; name: 'fieldPath', label: 'Field Path', title: 'Field Path', - type: PropertyType.Path + type: PropertyType.Path, + editable: true }] } }] @@ -118,7 +121,10 @@ export class AggregateBlock { }) public async tickCron(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); - const { aggregateType, groupByFields, disableUserGrouping } = ref.options; + + const options = await ref.getOptions(event.user); + + const { aggregateType, groupByFields, disableUserGrouping } = options; if (aggregateType !== 'period') { return; } @@ -179,9 +185,10 @@ export class AggregateBlock { */ private async sendCronDocuments(ref: AnyBlockType, userId: string, documents: AggregateVC[], actionStatus: RecordActionStep) { documents = await this.removeDocuments(ref, documents); - if (documents.length || ref.options.emptyData) { + const user = await PolicyUtils.getPolicyUserById(ref, userId); + const options = await ref.getOptions(user); + if (documents.length || options.emptyData) { const state: IPolicyEventState = { data: documents }; - const user = await PolicyUtils.getPolicyUserById(ref, userId); await ref.triggerEvents(PolicyOutputEventType.RunEvent, user, state, actionStatus); await ref.triggerEvents(PolicyOutputEventType.RefreshEvent, user, state, actionStatus); PolicyComponentsUtils.ExternalEventFn( @@ -248,7 +255,10 @@ export class AggregateBlock { output: [PolicyOutputEventType.RunEvent, PolicyOutputEventType.RefreshEvent] }) private async tickAggregate(ref: AnyBlockType, document: any, userId: string | null, actionStatus: RecordActionStep) { - const { expressions, condition, disableUserGrouping, groupByFields } = ref.options; + const user = await PolicyUtils.getDocumentOwner(ref, document, userId); + const options = await ref.getOptions(user); + + const { expressions, condition, disableUserGrouping, groupByFields } = options; const groupByUser = !disableUserGrouping; const filters: any = {}; @@ -282,7 +292,6 @@ export class AggregateBlock { } if (result === true) { - const user = await PolicyUtils.getDocumentOwner(ref, document, userId); rawEntities = await this.removeDocuments(ref, rawEntities); const state: IPolicyEventState = { data: rawEntities }; // actionStatus.saveResult(state); @@ -340,7 +349,8 @@ export class AggregateBlock { */ async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); - const { aggregateType } = ref.options; + const options = await ref.getOptions(event.user); + const { aggregateType } = options; const docs: IPolicyDocument | IPolicyDocument[] = event.data.data; if (Array.isArray(docs)) { @@ -363,4 +373,4 @@ export class AggregateBlock { return event.data; } -} +} \ No newline at end of file diff --git a/policy-service/src/policy-engine/blocks/button-block-addon.ts b/policy-service/src/policy-engine/blocks/button-block-addon.ts index 170eb8c0f4..d8395294eb 100644 --- a/policy-service/src/policy-engine/blocks/button-block-addon.ts +++ b/policy-service/src/policy-engine/blocks/button-block-addon.ts @@ -38,12 +38,14 @@ import { LocationType } from '@guardian/interfaces'; title: 'Button Name', type: PropertyType.Input, required: true, + editable: true }, { name: 'uiClass', label: 'UI Class', title: 'UI Class', type: PropertyType.Input, + editable: true }, { name: 'dialog', @@ -51,13 +53,15 @@ import { LocationType } from '@guardian/interfaces'; title: 'Dialog', type: PropertyType.Checkbox, default: false, + editable: true }, { name: 'hideWhenDiscontinued', label: 'Hide when discontinued', title: 'Hide when discontinued', type: PropertyType.Checkbox, - default: false + default: false, + editable: true }, { name: 'dialogOptions', @@ -71,12 +75,14 @@ import { LocationType } from '@guardian/interfaces'; title: 'Dialog Title', type: PropertyType.Input, required: true, + editable: true }, { name: 'dialogDescription', label: 'Dialog Description', title: 'Dialog Description', type: PropertyType.Input, + editable: true }, { name: 'dialogResultFieldPath', @@ -85,6 +91,7 @@ import { LocationType } from '@guardian/interfaces'; type: PropertyType.Path, required: true, default: 'option.comment', + editable: true }, ], visible: 'dialog === true', @@ -100,6 +107,7 @@ export class ButtonBlockAddon { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -108,7 +116,7 @@ export class ButtonBlockAddon { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - ...ref.options, + ...options, }; return data; } @@ -128,6 +136,7 @@ export class ButtonBlockAddon { actionStatus ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const parent = PolicyComponentsUtils.GetBlockRef( ref.parent ); @@ -136,10 +145,10 @@ export class ButtonBlockAddon { ref.tag, blockData.documentId, (document: any) => { - if (ref.options.dialog) { + if (options.dialog) { document = setOptions( document, - ref.options.dialogOptions.dialogResultFieldPath, + options.dialogOptions.dialogResultFieldPath, blockData.dialogResult ); } diff --git a/policy-service/src/policy-engine/blocks/button-block.ts b/policy-service/src/policy-engine/blocks/button-block.ts index d9ed05e469..f5f4afa66a 100644 --- a/policy-service/src/policy-engine/blocks/button-block.ts +++ b/policy-service/src/policy-engine/blocks/button-block.ts @@ -36,6 +36,7 @@ export class ButtonBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -44,9 +45,9 @@ export class ButtonBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - type: ref.options.type, - uiMetaData: ref.options.uiMetaData, - user: ref.options.user, + type: options.type, + uiMetaData: options.uiMetaData, + user: options.user, userId: user ? user.userId : null, userDid: user ? user.did : null, diff --git a/policy-service/src/policy-engine/blocks/calculate-block.ts b/policy-service/src/policy-engine/blocks/calculate-block.ts index 8e3768996b..c6278042c7 100644 --- a/policy-service/src/policy-engine/blocks/calculate-block.ts +++ b/policy-service/src/policy-engine/blocks/calculate-block.ts @@ -51,7 +51,8 @@ interface IMetadata { name: 'unsigned', label: 'Unsigned VC', title: 'Unsigned document', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }] }, variables: [ @@ -71,9 +72,12 @@ export class CalculateContainerBlock { documents: IVC | IVC[], ref: IPolicyCalculateBlock, parents: IPolicyDocument | IPolicyDocument[], - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { - const fields = ref.options.inputFields; + const options = await ref.getOptions(user); + + const fields = options.inputFields; let scope = {}; let docOwner: PolicyUser; if (Array.isArray(parents)) { @@ -101,8 +105,8 @@ export class CalculateContainerBlock { scope = await addon.run(scope, docOwner); } const newJson: any = {}; - if (ref.options.outputFields) { - for (const field of ref.options.outputFields) { + if (options.outputFields) { + for (const field of options.outputFields) { if (scope[field.value]) { newJson[field.name] = scope[field.value]; } @@ -122,11 +126,14 @@ export class CalculateContainerBlock { documents: IPolicyDocument | IPolicyDocument[], ref: IPolicyCalculateBlock, userId: string | null, - actionStatus: RecordActionStep + actionStatus: RecordActionStep, + user?: PolicyUser ): Promise { const context = await ref.debugContext({ documents }); const contextDocuments = context.documents as IPolicyDocument | IPolicyDocument[]; + const options = await ref.getOptions(user); + const isArray = Array.isArray(contextDocuments); if (!contextDocuments || (isArray && !contextDocuments.length)) { throw new BlockActionError('Invalid VC', ref.blockType, ref.uuid); @@ -147,12 +154,12 @@ export class CalculateContainerBlock { } // --> - const newJson = await this.calculate(json, ref, contextDocuments, userId); - if (ref.options.unsigned) { + const newJson = await this.calculate(json, ref, contextDocuments, userId, user); + if (options.unsigned) { return await this.createUnsignedDocument(newJson, ref, actionStatus?.id); } else { - const metadata = await this.aggregateMetadata(contextDocuments, ref, userId); - return await this.createDocument(newJson, metadata, ref, userId, actionStatus?.id); + const metadata = await this.aggregateMetadata(contextDocuments, ref, userId, user); + return await this.createDocument(newJson, metadata, ref, userId, actionStatus?.id, user); } } @@ -165,11 +172,13 @@ export class CalculateContainerBlock { private async aggregateMetadata( documents: IPolicyDocument | IPolicyDocument[], ref: IPolicyCalculateBlock, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { const isArray = Array.isArray(documents); const firstDocument = isArray ? documents[0] : documents; const relationships = []; + let accounts: any = {}; let tokens: any = {}; let id: string; @@ -226,6 +235,7 @@ export class CalculateContainerBlock { ref: IPolicyCalculateBlock, userId: string | null, actionStatusId: string, + user?: PolicyUser ): Promise { const { owner, @@ -238,8 +248,9 @@ export class CalculateContainerBlock { } = metadata; // <-- new vc const VCHelper = new VcHelper(); + const options = await ref.getOptions(user); - const outputSchema = await PolicyUtils.loadSchemaByID(ref, ref.options.outputSchema); + const outputSchema = await PolicyUtils.loadSchemaByID(ref, options.outputSchema); const vcSubject: any = { ...SchemaHelper.getContext(outputSchema), ...json @@ -311,20 +322,21 @@ export class CalculateContainerBlock { @CatchErrors() public async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); - if (ref.options.inputDocuments === 'separate') { + if (options.inputDocuments === 'separate') { if (Array.isArray(event.data.data)) { const result: IPolicyDocument[] = []; for (const doc of event.data.data) { - const newVC = await this.process(doc, ref, event?.user?.userId, event.actionStatus); + const newVC = await this.process(doc, ref, event?.user?.userId, event.actionStatus, event.user); result.push(newVC) } event.data.data = result; } else { - event.data.data = await this.process(event.data.data, ref, event?.user?.userId, event.actionStatus); + event.data.data = await this.process(event.data.data, ref, event?.user?.userId, event.actionStatus, event.user); } } else { - event.data.data = await this.process(event.data.data, ref, event?.user?.userId, event.actionStatus); + event.data.data = await this.process(event.data.data, ref, event?.user?.userId, event.actionStatus, event.user); } // event.actionStatus.saveResult(event.data); diff --git a/policy-service/src/policy-engine/blocks/calculate-math-addon.ts b/policy-service/src/policy-engine/blocks/calculate-math-addon.ts index 9009634a3a..0ed23ba538 100644 --- a/policy-service/src/policy-engine/blocks/calculate-math-addon.ts +++ b/policy-service/src/policy-engine/blocks/calculate-math-addon.ts @@ -1,7 +1,7 @@ import { CalculateAddon } from '../helpers/decorators/index.js'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { IPolicyCalculateAddon } from '../policy-engine.interface.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyUser } from '../policy-user.js'; import { ExternalEvent, ExternalEventType } from '../interfaces/external-event.js'; import { LocationType } from '@guardian/interfaces'; @@ -22,7 +22,31 @@ import { LocationType } from '@guardian/interfaces'; control: ControlType.Special, input: null, output: null, - defaultEvent: false + defaultEvent: false, + properties: [{ + name: 'equations', + label: 'Equations', + title: 'Equations', + type: PropertyType.Array, + editable: true, + items: { + label: 'Field', + value: '@variable = @formula', + properties: [{ + name: 'variable', + label: 'Variable', + title: 'Variable', + type: PropertyType.Input, + editable: true + }, { + name: 'formula', + label: 'Formula', + title: 'Formula', + type: PropertyType.Input, + editable: true + }] + } + }] }, variables: [] }) @@ -33,8 +57,10 @@ export class CalculateMathAddon { */ public async run(scope: any, user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - if (ref.options.equations) { - for (const equation of ref.options.equations) { + const options = await ref.getOptions(user); + + if (options.equations) { + for (const equation of options.equations) { scope[equation.variable] = ref.evaluate(equation.formula, scope); } } @@ -50,6 +76,7 @@ export class CalculateMathAddon { */ public getVariables(variables: any): any { const ref = PolicyComponentsUtils.GetBlockRef(this); + if (ref.options.equations) { for (const equation of ref.options.equations) { variables[equation.variable] = equation.formula; diff --git a/policy-service/src/policy-engine/blocks/calculate-math-variables.ts b/policy-service/src/policy-engine/blocks/calculate-math-variables.ts index 2c69dc64a5..8e89dd000e 100644 --- a/policy-service/src/policy-engine/blocks/calculate-math-variables.ts +++ b/policy-service/src/policy-engine/blocks/calculate-math-variables.ts @@ -29,32 +29,38 @@ import { LocationType } from '@guardian/interfaces'; name: 'sourceSchema', label: 'Source schema', title: 'Source schema', - type: PropertyType.Schemas + type: PropertyType.Schemas, + editable: false }, { name: 'onlyOwnDocuments', label: 'Owned by User', title: 'Owned by User', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'onlyOwnByGroupDocuments', label: 'Owned by Group', title: 'Owned by Group', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'onlyAssignDocuments', label: 'Assigned to User', title: 'Assigned to User', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'onlyAssignByGroupDocuments', label: 'Assigned to Group', title: 'Assigned to Group', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'selectors', label: 'Selectors', title: 'Selectors', type: PropertyType.Array, + editable: true, items: { label: 'Selector', value: '@sourceField @selectorType @comparisonValue', @@ -62,12 +68,14 @@ import { LocationType } from '@guardian/interfaces'; name: 'sourceField', label: 'Source field', title: 'Source field', - type: PropertyType.Path + type: PropertyType.Path, + editable: true }, { name: 'selectorType', label: 'Selector type', title: 'Selector type', type: PropertyType.Select, + editable: true, items: [{ label: 'Equal', value: 'equal' @@ -86,12 +94,14 @@ import { LocationType } from '@guardian/interfaces'; name: 'comparisonValue', label: 'Comparison value', title: 'Comparison value', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }, { name: 'comparisonValueType', label: 'Comparison value type', title: 'Comparison value type', type: PropertyType.Select, + editable: true, items: [{ label: 'Constanta', value: 'const' @@ -107,6 +117,7 @@ import { LocationType } from '@guardian/interfaces'; label: 'Variables', title: 'Variables', type: PropertyType.Array, + editable: false, items: { label: 'Variable', value: 'var @variableName = @variablePath', @@ -114,12 +125,14 @@ import { LocationType } from '@guardian/interfaces'; name: 'variableName', label: 'Variable name', title: 'Variable name', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }, { name: 'variablePath', label: 'Variable Path', title: 'Variable Path', - type: PropertyType.Path + type: PropertyType.Path, + editable: true }] } }] @@ -145,25 +158,26 @@ export class CalculateMathVariables { */ public async run(scope: any, user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const filters: any = {}; - if (ref.options.onlyOwnDocuments) { + if (options.onlyOwnDocuments) { filters.owner = user.did; } - if (ref.options.onlyOwnByGroupDocuments) { + if (options.onlyOwnByGroupDocuments) { filters.group = user.group; } - if (ref.options.onlyAssignDocuments) { + if (options.onlyAssignDocuments) { filters.assignedTo = user.did; } - if (ref.options.onlyAssignByGroupDocuments) { + if (options.onlyAssignByGroupDocuments) { filters.assignedToGroup = user.group; } - if (ref.options.sourceSchema) { - filters.schema = ref.options.sourceSchema; + if (options.sourceSchema) { + filters.schema = options.sourceSchema; } - if (Array.isArray(ref.options.selectors)) { - for (const selector of ref.options.selectors) { + if (Array.isArray(options.selectors)) { + for (const selector of options.selectors) { const expr = filters[selector.sourceField] || {}; switch (selector.selectorType) { case 'equal': @@ -205,7 +219,7 @@ export class CalculateMathVariables { const data = await ref.databaseServer.getVcDocument(filters); if (data) { - for (const variable of ref.options.variables) { + for (const variable of options.variables) { scope[variable.variableName] = PolicyUtils.getObjectValue(data, variable.variablePath); } } @@ -221,8 +235,9 @@ export class CalculateMathVariables { * Get variables * @param variables */ - public getVariables(variables: any): any { + public options(variables: any): any { const ref = PolicyComponentsUtils.GetBlockRef(this); + if (ref.options.variables) { for (const variable of ref.options.variables) { variables[variable.variableName] = variable.variablePath; diff --git a/policy-service/src/policy-engine/blocks/container-block.ts b/policy-service/src/policy-engine/blocks/container-block.ts index 05ea531c4e..df5eb4d2ac 100644 --- a/policy-service/src/policy-engine/blocks/container-block.ts +++ b/policy-service/src/policy-engine/blocks/container-block.ts @@ -1,6 +1,6 @@ import { ContainerBlock } from '../helpers/decorators/container-block.js'; import { PolicyInputEventType } from '../interfaces/index.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { PolicyUser } from '../policy-user.js'; import { LocationType } from '@guardian/interfaces'; @@ -25,7 +25,31 @@ import { IPolicyGetData } from '@policy-engine/policy-engine.interface.js'; PolicyInputEventType.RefreshEvent, ], output: null, - defaultEvent: false + defaultEvent: false, + properties: [{ + name: 'uiMetaData', + label: 'UI', + title: 'UI Properties', + type: PropertyType.Group, + editable: true, + properties: [{ + name: 'title', + label: 'Title', + title: 'Title', + type: PropertyType.Input, + editable: true + },{ + name: 'type', + label: 'Type', + title: 'Type', + type: PropertyType.Select, + items: [ + { label: 'BLANK', value: 'blank' }, + { label: 'TABS', value: 'tabs' }, + ], + editable: true, + }] + }] }, variables: [] }) @@ -36,6 +60,8 @@ export class InterfaceContainerBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + return { id: ref.uuid, blockType: ref.blockType, @@ -44,7 +70,7 @@ export class InterfaceContainerBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - uiMetaData: ref.options?.uiMetaData + uiMetaData: options?.uiMetaData }; } } diff --git a/policy-service/src/policy-engine/blocks/create-token-block.ts b/policy-service/src/policy-engine/blocks/create-token-block.ts index 9f238f06f3..86bbf07857 100644 --- a/policy-service/src/policy-engine/blocks/create-token-block.ts +++ b/policy-service/src/policy-engine/blocks/create-token-block.ts @@ -117,7 +117,9 @@ export class CreateTokenBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - if (ref.options.autorun) { + const options = await ref.getOptions(user); + + if (options.autorun) { throw new BlockActionError( `Block is autorunable and doesn't return any data`, ref.blockType, @@ -126,7 +128,7 @@ export class CreateTokenBlock { } const tokenTemplate = this._prepareTokenTemplate( ref, - PolicyUtils.getTokenTemplate(ref, ref.options.template), + PolicyUtils.getTokenTemplate(ref, options.template), Object.assign({}, this.state?.[user.id]?.data?.data, { index: this.state.tokenNumber, }) @@ -141,7 +143,7 @@ export class CreateTokenBlock { ), active: ref.isBlockActive(user), data: tokenTemplate, - ...ref.options, + ...options, }; } @@ -162,6 +164,8 @@ export class CreateTokenBlock { ); } + const options = await ref.getOptions(user); + const policyOwnerCred = await PolicyUtils.getUserCredentials(ref, ref.policyOwner, userId); if (!docs) { @@ -209,13 +213,13 @@ export class CreateTokenBlock { if (!doc.tokens) { doc.tokens = {}; } - doc.tokens[ref.options.template] = createdToken.tokenId; + doc.tokens[options.template] = createdToken.tokenId; } } else { if (!docs.tokens) { docs.tokens = {}; } - docs.tokens[ref.options.template] = createdToken.tokenId; + docs.tokens[options.template] = createdToken.tokenId; } delete this.state[user.id]; @@ -261,7 +265,9 @@ export class CreateTokenBlock { const ref = PolicyComponentsUtils.GetBlockRef(this); ref.log(`setData`); - if (ref.options.autorun) { + const options = await ref.getOptions(user); + + if (options.autorun) { throw new BlockActionError( `Block is autorunable and doesn't produce anything`, ref.blockType, @@ -285,7 +291,7 @@ export class CreateTokenBlock { template, this._prepareTokenTemplate( ref, - PolicyUtils.getTokenTemplate(ref, ref.options.template), + PolicyUtils.getTokenTemplate(ref, options.template), Object.assign({}, this.state?.[user.id]?.data?.data, { index: this.state.tokenNumber, }) @@ -322,6 +328,8 @@ export class CreateTokenBlock { const user = event.user; const eventData = event.data; + const options = await ref.getOptions(user); + if (!this.state.tokenNumber) { this.state.tokenNumber = 0; } @@ -329,13 +337,13 @@ export class CreateTokenBlock { this.state.tokenNumber++; await ref.saveState(); - if (ref.options.autorun) { + if (options.autorun) { await this._createToken( user, ref, this._prepareTokenTemplate( ref, - PolicyUtils.getTokenTemplate(ref, ref.options.template), + PolicyUtils.getTokenTemplate(ref, options.template), Object.assign({}, eventData.data, { index: this.state.tokenNumber, }) diff --git a/policy-service/src/policy-engine/blocks/custom-logic-block.ts b/policy-service/src/policy-engine/blocks/custom-logic-block.ts index 4616b14a4b..4fe184a3e4 100644 --- a/policy-service/src/policy-engine/blocks/custom-logic-block.ts +++ b/policy-service/src/policy-engine/blocks/custom-logic-block.ts @@ -59,19 +59,22 @@ interface IMetadata { name: 'unsigned', label: 'Unsigned VC', title: 'Unsigned document', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'passOriginal', label: 'Pass original', title: 'Pass original document', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'selectedScriptLanguage', label: 'Script Language', title: 'Select script language', type: PropertyType.Select, + editable: true, items: [ { label: 'JavaScript', @@ -113,6 +116,7 @@ export class CustomLogicBlock { @CatchErrors() public async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + try { const triggerEvents = async (documents: IPolicyDocument | IPolicyDocument[]) => { if (!documents) { @@ -174,6 +178,8 @@ export class CustomLogicBlock { return new Promise(async (resolve, reject) => { try { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + let documents: IPolicyDocument[]; if (Array.isArray(state.data)) { documents = state.data; @@ -182,7 +188,7 @@ export class CustomLogicBlock { } let metadata: IMetadata; - if (ref.options.unsigned) { + if (options.unsigned) { metadata = null; } else { if (!documents || !documents.length) { @@ -205,13 +211,13 @@ export class CustomLogicBlock { return; } const processing = async (json: any): Promise => { - if (ref.options.passOriginal) { + if (options.passOriginal) { return json; } - if (ref.options.unsigned) { + if (options.unsigned) { return await this.createUnsignedDocument(json, ref, actionStatus?.id); } else { - return await this.createDocument(json, metadata, ref, userId, actionStatus?.id); + return await this.createDocument(json, metadata, ref, userId, actionStatus?.id, user); } } if (Array.isArray(result)) { @@ -245,7 +251,7 @@ export class CustomLogicBlock { } } - const files = Array.isArray(ref.options.artifacts) ? ref.options.artifacts : []; + const files = Array.isArray(options.artifacts) ? options.artifacts : []; const execCodeArtifacts = files.filter((file: any) => file.type === ArtifactType.EXECUTABLE_CODE); let execCode = ''; for (const execCodeArtifact of execCodeArtifacts) { // todo for python??? @@ -273,8 +279,8 @@ export class CustomLogicBlock { collectTablesPack(context.documents, tablesPack); - const expression = ref.options.expression || ''; - if (ref.options.selectedScriptLanguage === ScriptLanguageOption.PYTHON) { + const expression = options.expression || ''; + if (options.selectedScriptLanguage === ScriptLanguageOption.PYTHON) { const worker = new Worker( path.join(path.dirname(filename), '..', 'helpers', 'workers', 'custom-logic-python-worker.js'), { @@ -359,6 +365,7 @@ export class CustomLogicBlock { const owner = await PolicyUtils.getDocumentOwner(ref, firstDocument, userId); const relayerAccount = await PolicyUtils.getDocumentRelayerAccount(ref, firstDocument, userId); const relationships = []; + const options = await ref.getOptions(user); let accounts: any = {}; let tokens: any = {}; let id: string; @@ -400,7 +407,7 @@ export class CustomLogicBlock { } let issuer: string; - switch (ref.options.documentSigner) { + switch (options.documentSigner) { case 'owner': issuer = owner.did; break; @@ -427,6 +434,7 @@ export class CustomLogicBlock { ref: IPolicyCalculateBlock, userId: string | null, actionStatusId: string, + user?: PolicyUser ): Promise { const { owner, @@ -442,7 +450,9 @@ export class CustomLogicBlock { // <-- new vc const VCHelper = new VcHelper(); - const outputSchema = await PolicyUtils.loadSchemaByID(ref, ref.options.outputSchema); + const options = await ref.getOptions(user); + + const outputSchema = await PolicyUtils.loadSchemaByID(ref, options.outputSchema); const vcSubject: any = { ...SchemaHelper.getContext(outputSchema), ...json @@ -467,7 +477,7 @@ export class CustomLogicBlock { const newId = await PolicyActionsUtils.generateId({ ref, - type: ref.options.idType, + type: options.idType, user: owner, relayerAccount, userId diff --git a/policy-service/src/policy-engine/blocks/data-transformation-addon.ts b/policy-service/src/policy-engine/blocks/data-transformation-addon.ts index 7c1e300f2e..49b5691f38 100644 --- a/policy-service/src/policy-engine/blocks/data-transformation-addon.ts +++ b/policy-service/src/policy-engine/blocks/data-transformation-addon.ts @@ -1,6 +1,6 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { IPolicyAddonBlock, IPolicyCalculateBlock, IPolicyDocument, IPolicyEventState } from '../policy-engine.interface.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyUser } from '../policy-user.js'; import { fileURLToPath } from 'url'; import { Worker } from 'node:worker_threads'; @@ -29,6 +29,13 @@ const filename = fileURLToPath(import.meta.url); ], output: null, defaultEvent: false, + properties: [{ + name: 'expression', + label: 'Expression', + title: 'Expression', + type: PropertyType.Code, + editable: true, + }] }, variables: [] }) diff --git a/policy-service/src/policy-engine/blocks/document-validator-block.ts b/policy-service/src/policy-engine/blocks/document-validator-block.ts index 91623a7323..32bf2ec5ab 100644 --- a/policy-service/src/policy-engine/blocks/document-validator-block.ts +++ b/policy-service/src/policy-engine/blocks/document-validator-block.ts @@ -2,7 +2,7 @@ import { BlockActionError } from '../errors/index.js'; import { ActionCallback, ValidatorBlock } from '../helpers/decorators/index.js'; import { CatchErrors } from '../helpers/decorators/catch-errors.js'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '../interfaces/index.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { AnyBlockType, IPolicyDocument, IPolicyEventState, IPolicyValidatorBlock } from '../policy-engine.interface.js'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { PolicyUtils } from '../helpers/utils.js'; @@ -33,7 +33,96 @@ import { LocationType } from '@guardian/interfaces'; PolicyOutputEventType.RefreshEvent, PolicyOutputEventType.ErrorEvent ], - defaultEvent: true + defaultEvent: true, + properties: [{ + name: 'conditions', + label: 'Conditions', + title: 'Conditions', + type: PropertyType.Array, + editable: true, + items: { + label: 'Condition', + value: '', + properties: [ + { + name: 'type', + label: 'Type', + title: 'Type', + type: PropertyType.Select, + items: [ + { label: 'Equal', value: 'equal' }, + { label: 'Not Equal', value: 'not_equal' }, + { label: 'In', value: 'in' }, + { label: 'Not In', value: 'not_in' } + ], + editable: true + }, + { + name: 'field', + label: 'Field', + title: 'Field', + type: PropertyType.Input, + editable: true + }, + { + name: 'value', + label: 'Value', + title: 'Value', + type: PropertyType.Input, + editable: true + }, + ] + } + }, + { + name: 'documentType', + label: 'Document Type', + title: 'Document Type', + type: PropertyType.Select, + items: [ + { label: 'VC Document', value: 'vc-document'}, + { label: 'VP Document', value: 'vp-document'}, + { label: 'Related VC Document', value: 'related-vc-document'}, + { label: 'Related VP Document', value: 'related-vp-document'} + ], + editable: false + }, + { + name: 'schema', + label: 'Check Schema', + title: 'Check Schema', + type: PropertyType.Schemas, + editable: true + }, + { + name: 'checkOwnerDocument', + label: 'Check Owned by User', + title: 'Check Owned by User', + type: PropertyType.Checkbox, + editable: true + }, + { + name: 'checkOwnerByGroupDocument', + label: 'Check Owned by Group', + title: 'Check Owned by Group', + type: PropertyType.Checkbox, + editable: true + }, + { + name: 'checkAssignDocument', + label: 'Check Assigned to User', + title: 'Check Assigned to User', + type: PropertyType.Checkbox, + editable: true + }, + { + name: 'checkAssignByGroupDocument', + label: 'Check Assigned to Group', + title: 'Check Assigned to Group', + type: PropertyType.Checkbox, + editable: true + }, + ] }, variables: [ { path: 'options.schema', alias: 'schema', type: 'Schema' } @@ -71,7 +160,9 @@ export class DocumentValidatorBlock { const documentRef = PolicyUtils.getDocumentRef(document); - if (ref.options.documentType === 'related-vc-document') { + const options = await ref.getOptions(event.user); + + if (options.documentType === 'related-vc-document') { if (documentRef) { document = await ref.databaseServer.getVcDocument({ 'policyId': { $eq: ref.policyId }, @@ -82,7 +173,7 @@ export class DocumentValidatorBlock { } } - if (ref.options.documentType === 'related-vp-document') { + if (options.documentType === 'related-vp-document') { if (documentRef) { document = await ref.databaseServer.getVpDocument({ 'policyId': ref.policyId, @@ -99,19 +190,19 @@ export class DocumentValidatorBlock { const documentType = PolicyUtils.getDocumentType(document); - if (ref.options.documentType === 'vc-document') { + if (options.documentType === 'vc-document') { if (documentType !== 'VerifiableCredential') { return `Invalid document type`; } - } else if (ref.options.documentType === 'vp-document') { + } else if (options.documentType === 'vp-document') { if (documentType !== 'VerifiablePresentation') { return `Invalid document type`; } - } else if (ref.options.documentType === 'related-vc-document') { + } else if (options.documentType === 'related-vc-document') { if (documentType !== 'VerifiableCredential') { return `Invalid document type`; } - } else if (ref.options.documentType === 'related-vp-document') { + } else if (options.documentType === 'related-vp-document') { if (documentType !== 'VerifiablePresentation') { return `Invalid document type`; } @@ -120,36 +211,36 @@ export class DocumentValidatorBlock { const userDID = event?.user?.did; const userGroup = event?.user?.group; - if (ref.options.checkOwnerDocument) { + if (options.checkOwnerDocument) { if (document.owner !== userDID) { return `Invalid owner`; } } - if (ref.options.checkOwnerByGroupDocument) { + if (options.checkOwnerByGroupDocument) { if (document.group !== userGroup) { return `Invalid group`; } } - if (ref.options.checkAssignDocument) { + if (options.checkAssignDocument) { if (document.assignedTo !== userDID) { return `Invalid assigned user`; } } - if (ref.options.checkAssignByGroupDocument) { + if (options.checkAssignByGroupDocument) { if (document.assignedToGroup !== userGroup) { return `Invalid assigned group`; } } - if (ref.options.schema) { - const schema = await PolicyUtils.loadSchemaByID(ref, ref.options.schema); + if (options.schema) { + const schema = await PolicyUtils.loadSchemaByID(ref, options.schema); if (!PolicyUtils.checkDocumentSchema(ref, document, schema)) { return `Invalid document schema`; } } - if (ref.options.conditions) { - for (const filter of ref.options.conditions) { + if (options.conditions) { + for (const filter of options.conditions) { if (!PolicyUtils.checkDocumentField(document, filter)) { return `Invalid document`; } diff --git a/policy-service/src/policy-engine/blocks/documents-source-addon.ts b/policy-service/src/policy-engine/blocks/documents-source-addon.ts index 1d88fa0c37..487e1a9388 100644 --- a/policy-service/src/policy-engine/blocks/documents-source-addon.ts +++ b/policy-service/src/policy-engine/blocks/documents-source-addon.ts @@ -2,7 +2,7 @@ import { SourceAddon, StateField } from '../helpers/decorators/index.js'; import { BlockActionError } from '../errors/index.js'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { IPolicyAddonBlock, IPolicyDocument } from '../policy-engine.interface.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyUser } from '../policy-user.js'; import { PolicyUtils, QueryType } from '../helpers/utils.js'; import ObjGet from 'lodash.get'; @@ -24,7 +24,114 @@ import { LocationType } from '@guardian/interfaces'; control: ControlType.Special, input: null, output: null, - defaultEvent: false + defaultEvent: false, + properties: [{ + name: 'dataType', + label: 'Data Type', + title: 'Data Type', + type: PropertyType.Select, + items: [ + { label: 'Collection (VC)', value: 'vc-documents' }, + { label: 'Collection (DID)', value: 'did-documents' }, + { label: 'Collection (Approve)', value: 'approve' }, + { label: 'Collection (VP)', value: 'vp-documents' } + ], + editable: true + }, { + name: 'schema', + label: 'Schema', + title: 'Schema', + type: PropertyType.Schemas, + editable: true + }, { + name: 'onlyOwnDocuments', + label: 'Owned by User', + title: 'Owned by User', + type: PropertyType.Checkbox, + editable: true + }, { + name: 'onlyOwnByGroupDocuments', + label: 'Owned by Group', + title: 'Owned by Group', + type: PropertyType.Checkbox, + editable: true + }, { + name: 'onlyAssignDocuments', + label: 'Assigned to User', + title: 'Assigned to User', + type: PropertyType.Checkbox, + editable: true + }, { + name: 'onlyAssignByGroupDocuments', + label: 'Assigned to Group', + title: 'Assigned to Group', + type: PropertyType.Checkbox, + editable: true + }, { + name: 'hidePreviousVersions', + label: 'Hide Previous Versions', + title: 'Hide Previous Versions', + type: PropertyType.Checkbox, + editable: true + }, { + name: 'orderField', + label: 'Order Field', + title: 'Order Field', + type: PropertyType.Input, + editable: true + }, { + name: 'orderDirection', + label: 'Order Direction', + title: 'Order Direction', + type: PropertyType.Select, + items: [ + { label: 'None', value: '' }, + { label: 'ASC', value: 'ASC' }, + { label: 'DESC', value: 'DESC' } + ], + editable: true + }, { + name: 'filters', + label: 'Filters', + title: 'Filters', + type: PropertyType.Array, + editable: true, + items: { + label: 'Field', + value: '', + properties: [{ + name: 'type', + label: 'Type', + title: 'Type', + type: PropertyType.Select, + items: [ + { label: 'Equal', value: 'equal'}, + { label: 'Not Equal', value: 'not_equal'}, + { label: 'In', value: 'in'}, + { label: 'Not In', value: 'not_in'}, + { label: 'Greater Than', value: 'gt'}, + { label: 'Greater Than or Equal', value: 'gte'}, + { label: 'Less Than', value: 'lt'}, + { label: 'Less Than or Equal', value: 'lte'}, + { label: 'User Defined', value: 'user_defined'} + ], + editable: true + }, { + name: 'field', + label: 'Field', + title: 'Field', + type: PropertyType.Path, + editable: false + }, { + name: 'value', + label: 'Value', + title: 'Value', + type: PropertyType.Input, + editable: true + }] + } + } + ] }, variables: [ { path: 'options.schema', alias: 'schema', type: 'Schema' } @@ -87,35 +194,36 @@ export class DocumentsSourceAddon { otherOptions?: any ) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const filters: any = {}; - if (!Array.isArray(ref.options.filters)) { + if (!Array.isArray(options.filters)) { throw new BlockActionError('filters option must be an array', ref.blockType, ref.uuid); } - if (ref.options.onlyOwnDocuments) { + if (options.onlyOwnDocuments) { filters.owner = user.did; } - if (ref.options.onlyOwnByGroupDocuments) { + if (options.onlyOwnByGroupDocuments) { filters.group = user.group; } - if (ref.options.onlyAssignDocuments) { + if (options.onlyAssignDocuments) { filters.assignedTo = user.did; } - if (ref.options.onlyAssignByGroupDocuments) { + if (options.onlyAssignByGroupDocuments) { filters.assignedToGroup = user.group; } - if (ref.options.hidePreviousVersions) { + if (options.hidePreviousVersions) { filters.edited = { $ne: true }; } - if (ref.options.schema) { - filters.schema = ref.options.schema; + if (options.schema) { + filters.schema = options.schema; } filters.initId = { $exists: false } - for (const filter of ref.options.filters) { + for (const filter of options.filters) { const expr = filters[filter.field] || {}; const query = PolicyUtils.parseQuery(filter.type, filter.value); @@ -150,17 +258,17 @@ export class DocumentsSourceAddon { } else { otherOptions.orderBy.createDate = stateData.orderDirection; } - } else if (ref.options.orderDirection) { + } else if (options.orderDirection) { otherOptions.orderBy = {}; - if (ref.options.orderField) { - otherOptions.orderBy[ref.options.orderField] = ref.options.orderDirection; + if (options.orderField) { + otherOptions.orderBy[options.orderField] = options.orderDirection; } else { - otherOptions.orderBy.createDate = ref.options.orderDirection; + otherOptions.orderBy.createDate = options.orderDirection; } } let data: IPolicyDocument[] | number; - switch (ref.options.dataType) { + switch (options.dataType) { case 'vc-documents': filters.policyId = ref.policyId; data = await ref.databaseServer.getVcDocuments(filters, otherOptions, countResult) as number | IPolicyDocument[]; @@ -203,7 +311,7 @@ export class DocumentsSourceAddon { data = await PolicyUtils.getAllStandardRegistryAccounts(ref, countResult, user.userId); break; default: - throw new BlockActionError(`dataType "${ref.options.dataType}" is unknown`, ref.blockType, ref.uuid) + throw new BlockActionError(`dataType "${options.dataType}" is unknown`, ref.blockType, ref.uuid) } if (!countResult) { @@ -236,33 +344,33 @@ export class DocumentsSourceAddon { */ async getFromSourceFilters(user: PolicyUser, globalFilters: any) { const ref = PolicyComponentsUtils.GetBlockRef(this); - + const options = await ref.getOptions(user); const filters: any = []; - if (!Array.isArray(ref.options.filters)) { + if (!Array.isArray(options.filters)) { throw new BlockActionError('filters option must be an array', ref.blockType, ref.uuid); } - if (ref.options.onlyOwnDocuments) { + if (options.onlyOwnDocuments) { filters.push({ $eq: [user.did, '$owner'] }); } - if (ref.options.onlyOwnByGroupDocuments) { + if (options.onlyOwnByGroupDocuments) { filters.push({ $eq: [user.group, '$group'] }); } - if (ref.options.onlyAssignDocuments) { + if (options.onlyAssignDocuments) { filters.push({ $eq: [user.did, '$assignedTo'] }); } - if (ref.options.onlyAssignByGroupDocuments) { + if (options.onlyAssignByGroupDocuments) { filters.push({ $eq: [user.group, '$assignedToGroup'] }); } - if (ref.options.hidePreviousVersions) { + if (options.hidePreviousVersions) { filters.push({ $ne: [true, '$assignedToGroup'] }); } - if (ref.options.schema) { - filters.push({ $eq: [ref.options.schema, '$schema'] }); + if (options.schema) { + filters.push({ $eq: [options.schema, '$schema'] }); } - for (const filter of ref.options.filters) { + for (const filter of options.filters) { const queryType = filter.type as QueryType; const queryValue = PolicyUtils.getQueryValue(queryType, filter.value); const queryExpression = PolicyUtils.getQueryExpression(queryType, queryValue); diff --git a/policy-service/src/policy-engine/blocks/documents-source.ts b/policy-service/src/policy-engine/blocks/documents-source.ts index 67b6e97005..9b9b566f8d 100644 --- a/policy-service/src/policy-engine/blocks/documents-source.ts +++ b/policy-service/src/policy-engine/blocks/documents-source.ts @@ -64,7 +64,8 @@ export class InterfaceDocumentsSource { async onAddonEvent(user: PolicyUser, tag: string, documentId: string, handler: (document: any) => Promise, actionStatus) { const ref = PolicyComponentsUtils.GetBlockRef(this); - const fields = ref.options?.uiMetaData?.fields?.filter((field) => + const options = await ref.getOptions(user); + const fields = options?.uiMetaData?.fields?.filter((field) => field?.bindBlocks?.includes(tag) ); @@ -72,7 +73,7 @@ export class InterfaceDocumentsSource { const savepointIds = saved.__savepointIds as string[] | undefined; const enableCommonSorting = - !!ref.options?.uiMetaData?.enableSorting + !!options?.uiMetaData?.enableSorting const sourceAddons = fields ?.filter((field) => field.bindGroup) @@ -147,6 +148,7 @@ export class InterfaceDocumentsSource { */ async getData(user: PolicyUser, uuid: string, queryParams: any): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); let ret: IPolicyGetData = { id: ref.uuid, @@ -228,7 +230,7 @@ export class InterfaceDocumentsSource { return addon.blockType === 'historyAddon'; }) as IPolicyAddonBlock; - const enableCommonSorting = ref.options.uiMetaData.enableSorting || (sortDirection && sortField) + const enableCommonSorting = options.uiMetaData.enableSorting || (sortDirection && sortField) let sortState = this.state[user.id] || {}; if (sortDirection && sortField) { @@ -328,7 +330,7 @@ export class InterfaceDocumentsSource { blocks: filters, commonAddons, }, - Object.assign(ref.options.uiMetaData, { + Object.assign(options.uiMetaData, { viewHistory: !!history, }), sortState diff --git a/policy-service/src/policy-engine/blocks/dropdown-block-addon.ts b/policy-service/src/policy-engine/blocks/dropdown-block-addon.ts index f9fba23a03..6abe89bc01 100644 --- a/policy-service/src/policy-engine/blocks/dropdown-block-addon.ts +++ b/policy-service/src/policy-engine/blocks/dropdown-block-addon.ts @@ -40,6 +40,7 @@ import { LocationType } from '@guardian/interfaces'; title: 'Option Name', type: PropertyType.Path, required: true, + editable: true }, { name: 'optionValue', @@ -47,6 +48,7 @@ import { LocationType } from '@guardian/interfaces'; title: 'Option Value', type: PropertyType.Path, required: true, + editable: true }, { name: 'field', @@ -54,6 +56,7 @@ import { LocationType } from '@guardian/interfaces'; title: 'Field', type: PropertyType.Path, required: true, + editable: true }, ], }, @@ -66,7 +69,7 @@ export class DropdownBlockAddon { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - + const options = await ref.getOptions(user); const documents: any[] = await ref.getSources(user, null); const data: IPolicyGetData = { @@ -77,11 +80,11 @@ export class DropdownBlockAddon { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - ...ref.options, + ...options, documents: documents.map((e) => { return { - name: findOptions(e, ref.options.optionName), - optionValue: findOptions(e, ref.options.optionValue), + name: findOptions(e, options.optionName), + optionValue: findOptions(e, options.optionValue), value: e.id, }; }), @@ -104,6 +107,7 @@ export class DropdownBlockAddon { actionStatus ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const documents: any[] = await ref.getSources(user, null); const dropdownDocument = documents.find( // tslint:disable-next-line:no-shadowed-variable @@ -126,8 +130,8 @@ export class DropdownBlockAddon { (document: any) => { document = setOptions( document, - ref.options.field, - findOptions(dropdownDocument, ref.options.optionValue) + options.field, + findOptions(dropdownDocument, options.optionValue) ); return { data: document, diff --git a/policy-service/src/policy-engine/blocks/external-data-block.ts b/policy-service/src/policy-engine/blocks/external-data-block.ts index 7cae363f4a..b20fb7e1ad 100644 --- a/policy-service/src/policy-engine/blocks/external-data-block.ts +++ b/policy-service/src/policy-engine/blocks/external-data-block.ts @@ -3,7 +3,7 @@ import { DocumentSignature, LocationType, Schema } from '@guardian/interfaces'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { CatchErrors } from '../helpers/decorators/catch-errors.js'; import { PolicyOutputEventType } from '../interfaces/index.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { AnyBlockType, IPolicyDocument, IPolicyEventState, IPolicyValidatorBlock } from '../policy-engine.interface.js'; import { BlockActionError } from '../errors/index.js'; import { PolicyUser } from '../policy-user.js'; @@ -36,7 +36,40 @@ import { RecordActionStep } from '../record-action-step.js'; PolicyOutputEventType.RefreshEvent, PolicyOutputEventType.ErrorEvent ], - defaultEvent: true + defaultEvent: true, + properties: [{ + name: 'entityType', + label: 'Entity Type', + title: 'Entity Type', + type: PropertyType.Input, + editable: false + }, + { + name: 'schema', + label: 'Schema', + title: 'Schema', + type: PropertyType.Schemas, + editable: false + }, + { + name: 'relayerAccount', + label: 'Set Relayer Account', + title: 'Set Relayer Account', + type: PropertyType.Checkbox, + editable: false + }, + { + name: 'forceRelayerAccount', + label: 'Force User Account', + title: 'Force User Account', + type: PropertyType.Select, + items: [ + { label: '', value: '' }, + { label: 'Pre-set user account', value: 'preset' }, + { label: 'Current user account', value: 'current' }, + ], + editable: false + }] }, variables: [ { path: 'options.schema', alias: 'schema', type: 'Schema' } @@ -161,10 +194,11 @@ export class ExternalDataBlock { } const user: PolicyUser = await PolicyUtils.getDocumentOwner(ref, data, null); + const options = await ref.getOptions(user); const documentRef = await this.getRelationships(ref, data.ref); const schema = await this.getSchema(); const vc = VcDocument.fromJsonTree(data.document); - const forceRelayerAccount = ref.options.forceRelayerAccount; + const forceRelayerAccount = options.forceRelayerAccount; const inheritRelayerAccount = PolicyComponentsUtils.IsInheritRelayerAccount(ref.policyId, forceRelayerAccount); //Relayer Account @@ -178,8 +212,8 @@ export class ExternalDataBlock { const tags = await PolicyUtils.getBlockTags(ref); PolicyUtils.setDocumentTags(doc, tags); - doc.type = ref.options.entityType; - doc.schema = ref.options.schema; + doc.type = options.entityType; + doc.schema = options.schema; doc.accounts = accounts; doc.relayerAccount = relayerAccount; doc.signature = (verify ? diff --git a/policy-service/src/policy-engine/blocks/external-topic-block.ts b/policy-service/src/policy-engine/blocks/external-topic-block.ts index 6a41c3a4c7..4b71fc2e13 100644 --- a/policy-service/src/policy-engine/blocks/external-topic-block.ts +++ b/policy-service/src/policy-engine/blocks/external-topic-block.ts @@ -118,7 +118,8 @@ interface SchemaItem { name: 'schema', label: 'Schema', title: 'Schema', - type: PropertyType.Schemas + type: PropertyType.Schemas, + editable: false }] }, variables: [ @@ -715,6 +716,7 @@ export class ExternalTopicBlock { actionStatus: RecordActionStep ): Promise { const documentRef = await this.getRelationships(ref, user); + const options = await ref.getOptions(user); if (message.type !== MessageType.VCDocument) { return; @@ -734,7 +736,7 @@ export class ExternalTopicBlock { const relayerAccount = await PolicyUtils.getRefRelayerAccount(ref, user.did, null, documentRef, user.userId); const result: IPolicyDocument = PolicyUtils.createPolicyDocument(ref, user, document); - result.schema = ref.options.schema; + result.schema = options.schema; result.relayerAccount = relayerAccount; if (documentRef) { PolicyUtils.setDocumentRef(result, documentRef); diff --git a/policy-service/src/policy-engine/blocks/extract-data-block.ts b/policy-service/src/policy-engine/blocks/extract-data-block.ts index 8513c4e3ae..f607a8adb2 100644 --- a/policy-service/src/policy-engine/blocks/extract-data-block.ts +++ b/policy-service/src/policy-engine/blocks/extract-data-block.ts @@ -37,6 +37,7 @@ import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfac label: 'Action', title: 'Action', type: PropertyType.Select, + editable: true, items: [ { label: 'Get', @@ -52,7 +53,8 @@ import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfac name: 'schema', label: 'Schema', title: 'Schema', - type: PropertyType.Schemas + type: PropertyType.Schemas, + editable: false }] }, variables: [ @@ -254,8 +256,9 @@ export class ExtractDataBlock { @CatchErrors() async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); - if (ref.options.action === 'set') { + if (options.action === 'set') { await this.setAction(ref, event); } else { await this.getAction(ref, event); @@ -263,7 +266,7 @@ export class ExtractDataBlock { PolicyComponentsUtils.ExternalEventFn( new ExternalEvent(ExternalEventType.Run, ref, event?.user, { - action: ref.options.action, + action: options.action, documents: ExternalDocuments(event.data.data) }) ); diff --git a/policy-service/src/policy-engine/blocks/filters-addon-block.ts b/policy-service/src/policy-engine/blocks/filters-addon-block.ts index c7dd109eaf..22ac3c23fb 100644 --- a/policy-service/src/policy-engine/blocks/filters-addon-block.ts +++ b/policy-service/src/policy-engine/blocks/filters-addon-block.ts @@ -53,20 +53,22 @@ export class FiltersAddonBlock { } } - private addQuery(filter: any, value: any) { + private async addQuery(filter: any, value: any, user?: PolicyUser) { const ref = PolicyComponentsUtils.GetBlockRef(this); - const query = PolicyUtils.parseQuery(ref.options.queryType || QueryType.eq, value); + const options = await ref.getOptions(user); + const query = PolicyUtils.parseQuery(options.queryType || QueryType.eq, value); if (query && query.expression) { - filter[ref.options.field] = query.expression; + filter[options.field] = query.expression; } else { throw new BlockActionError(`Unknown filter type: ${filter.type}`, ref.blockType, ref.uuid); } } - private checkValues(blockState: any, value: any): boolean { + private async checkValues(blockState: any, value: any, user: PolicyUser): Promise { if (Array.isArray(blockState.lastData)) { const ref = PolicyComponentsUtils.GetBlockRef(this); - const query = PolicyUtils.parseQuery(ref.options.queryType || QueryType.eq, value); + const options = await ref.getOptions(user); + const query = PolicyUtils.parseQuery(options.queryType || QueryType.eq, value); const itemValues = query.value; if (Array.isArray(itemValues)) { for (const itemValue of itemValues) { @@ -98,20 +100,21 @@ export class FiltersAddonBlock { public async getFilters(user: PolicyUser): Promise<{ [key: string]: string }> { const ref = PolicyComponentsUtils.GetBlockRef(this); const filters = ref.filters[user.id] || {}; + const options = await ref.getOptions(user); - if (!filters[ref.options.field] && !ref.options.canBeEmpty) { + if (!filters[options.field] && !options.canBeEmpty) { let filterValue: any; - if (ref.options.type === 'dropdown') { + if (options.type === 'dropdown') { const data: any[] = await ref.getSources(user, null); - filterValue = findOptions(data[0], ref.options.optionValue); + filterValue = findOptions(data[0], options.optionValue); } - if (ref.options.type === 'datepicker') { + if (options.type === 'datepicker') { filterValue = ''; } - if (ref.options.type === 'input') { + if (options.type === 'input') { filterValue = ''; } @@ -122,11 +125,11 @@ export class FiltersAddonBlock { } else { filterValue = ''; } - if (ref.options.queryType === 'user_defined') { + if (options.queryType === 'user_defined') { filterValue = 'eq:' + filterValue; } - this.addQuery(filters, filterValue) + await this.addQuery(filters, filterValue, user); } return filters; } @@ -137,6 +140,7 @@ export class FiltersAddonBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const block: IPolicyGetData = { id: ref.uuid, @@ -146,33 +150,33 @@ export class FiltersAddonBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - type: ref.options.type, - uiMetaData: ref.options.uiMetaData, - canBeEmpty: ref.options.canBeEmpty, - queryType: ref.options.queryType + type: options.type, + uiMetaData: options.uiMetaData, + canBeEmpty: options.canBeEmpty, + queryType: options.queryType }; const data: any[] = await ref.getSources(user, null); - if (ref.options.type === 'dropdown') { + if (options.type === 'dropdown') { const blockState = this.state[user.id] || {}; blockState.lastData = data.map((e) => { return { - name: findOptions(e, ref.options.optionName), - value: findOptions(e, ref.options.optionValue), + name: findOptions(e, options.optionName), + value: findOptions(e, options.optionValue), } }).filter((value, index, array) => { const i = array.findIndex(v => v.value === value.value); return i === index; }); block.data = blockState.lastData; - block.optionName = ref.options.optionName; - block.optionValue = ref.options.optionValue; + block.optionName = options.optionName; + block.optionValue = options.optionValue; block.filterValue = blockState.lastValue; this.state[user.id] = blockState; } - if (ref.options.type === 'datepicker' || ref.options.type === 'input') { + if (options.type === 'datepicker' || options.type === 'input') { const blockState = this.state[user.id] || {}; block.filterValue = blockState.lastValue; } @@ -194,6 +198,8 @@ export class FiltersAddonBlock { async setFiltersStrict(user: PolicyUser, data: any) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + this.previousState[user.id] = { ...this.state[user.id] }; const filter: any = {}; if (!data) { @@ -202,27 +208,27 @@ export class FiltersAddonBlock { const value = data.filterValue; const blockState = this.state[user.id] || {}; - if (ref.options.type === 'dropdown') { + if (options.type === 'dropdown') { if (!blockState.lastData) { await this.getData(user); } if (value) { this.addQuery(filter, value); - } else if (!ref.options.canBeEmpty) { + } else if (!options.canBeEmpty) { throw new BlockActionError(`filter value is unknown`, ref.blockType, ref.uuid) } } - if (ref.options.type === 'datepicker') { + if (options.type === 'datepicker') { if (value) { this.addQuery(filter, value); - } else if (!ref.options.canBeEmpty) { + } else if (!options.canBeEmpty) { throw new BlockActionError(`filter value is unknown`, ref.blockType, ref.uuid) } } - if (ref.options.type === 'input') { + if (options.type === 'input') { if (value) { this.addQuery(filter, value); - } else if (!ref.options.canBeEmpty) { + } else if (!options.canBeEmpty) { throw new BlockActionError(`filter value is unknown`, ref.blockType, ref.uuid) } } @@ -234,6 +240,8 @@ export class FiltersAddonBlock { async setFilterState(user: PolicyUser, data: any): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + this.previousState[user.id] = { ...this.state[user.id] }; const filter: any = {}; if (!data) { @@ -242,27 +250,27 @@ export class FiltersAddonBlock { const value = data.filterValue; const blockState = this.state[user.id] || {}; - if (ref.options.type === 'dropdown') { + if (options.type === 'dropdown') { if (!blockState.lastData) { await this.getData(user); } - if (this.checkValues(blockState, value)) { + if (await this.checkValues(blockState, value, user)) { this.addQuery(filter, value); - } else if (!ref.options.canBeEmpty) { + } else if (!options.canBeEmpty) { throw new BlockActionError(`filter value is unknown`, ref.blockType, ref.uuid) } } - if (ref.options.type === 'datepicker') { + if (options.type === 'datepicker') { if (value) { this.addQuery(filter, value); - } else if (!ref.options.canBeEmpty) { + } else if (!options.canBeEmpty) { throw new BlockActionError(`filter value is unknown`, ref.blockType, ref.uuid) } } - if (ref.options.type === 'input') { + if (options.type === 'input') { if (value) { this.addQuery(filter, value); - } else if (!ref.options.canBeEmpty) { + } else if (!options.canBeEmpty) { throw new BlockActionError(`filter value is unknown`, ref.blockType, ref.uuid) } } diff --git a/policy-service/src/policy-engine/blocks/global-events-reader-block.ts b/policy-service/src/policy-engine/blocks/global-events-reader-block.ts index 6a879a4313..a72bd19ecf 100644 --- a/policy-service/src/policy-engine/blocks/global-events-reader-block.ts +++ b/policy-service/src/policy-engine/blocks/global-events-reader-block.ts @@ -99,6 +99,7 @@ type WorkerTopicMessageRaw = { title: 'Show button to move to next block with cached payload', type: PropertyType.Checkbox, default: false, + editable: true }, { name: 'eventTopics', @@ -113,7 +114,8 @@ type WorkerTopicMessageRaw = { name: 'topicId', label: 'Topic ID', title: 'Hedera topic id (0.0.x)', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }, { name: 'active', @@ -121,6 +123,7 @@ type WorkerTopicMessageRaw = { title: 'Add this topic stream as active for new users', type: PropertyType.Checkbox, default: true, + editable: true } ] } @@ -130,6 +133,7 @@ type WorkerTopicMessageRaw = { label: 'Branches', title: 'Branch outputs', type: PropertyType.Array, + editable: true, items: { label: 'Branch', value: '@branchEvent', @@ -138,7 +142,8 @@ type WorkerTopicMessageRaw = { name: 'branchEvent', label: 'Branch event', title: 'Output event name', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }, { name: 'documentType', @@ -147,12 +152,14 @@ type WorkerTopicMessageRaw = { type: PropertyType.Select, items: GLOBAL_DOCUMENT_TYPE_ITEMS, default: GLOBAL_DOCUMENT_TYPE_DEFAULT, + editable: true }, { name: 'schema', label: 'Schema', title: 'Local policy schema (validate VC before routing)', type: PropertyType.Schemas, + editable: false } ] } @@ -182,6 +189,8 @@ class GlobalEventsReaderBlock { user.userId ); + const options = await ref.getOptions(user); + const existingTopicIds = new Set(); for (const stream of existingStreams) { @@ -191,7 +200,7 @@ class GlobalEventsReaderBlock { } } - const config = ref.options; + const config = options; const optionTopicIds = config.eventTopics ?? []; const defaultBranchDocumentTypeByBranch: Record = {}; @@ -903,7 +912,9 @@ class GlobalEventsReaderBlock { user: PolicyUser, stream: GlobalEventsReaderStream ): Promise { - const config = (ref.options || {}) as GlobalEventReaderConfig; + const options = await ref.getOptions(user); + + const config = (options || {}) as GlobalEventReaderConfig; const branches = config.branches ?? []; if (!stream.globalTopicId) { @@ -1081,7 +1092,8 @@ class GlobalEventsReaderBlock { public async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - const config = ref.options; + const options = await ref.getOptions(user); + const config = options; if (ref.dryRun) { return { @@ -1242,6 +1254,7 @@ class GlobalEventsReaderBlock { actionStatus ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); if (ref.dryRun) { throw new BlockActionError('Block is disabled in dry run mode', ref.blockType, ref.uuid); @@ -1253,7 +1266,7 @@ class GlobalEventsReaderBlock { throw new BlockActionError('Invalid operation', ref.blockType, ref.uuid); } - const config = ref.options || {}; + const config = options || {}; const configuredTopicIdSet = new Set(this.extractConfiguredTopicIds(config)); const value: SetDataPayloadReader | { streams: [] } = data.value ?? { streams: [] }; diff --git a/policy-service/src/policy-engine/blocks/global-events-writer-block.ts b/policy-service/src/policy-engine/blocks/global-events-writer-block.ts index 6e8d783f0f..1e8e5de1a4 100644 --- a/policy-service/src/policy-engine/blocks/global-events-writer-block.ts +++ b/policy-service/src/policy-engine/blocks/global-events-writer-block.ts @@ -50,12 +50,14 @@ interface SetDataPayload { title: 'Show button to move to next block with cached payload', type: PropertyType.Checkbox, default: false, + editable: true }, { name: 'topicIds', label: 'Global topics', title: 'One or more Hedera topics where notifications are published', type: PropertyType.Array, + editable: true, items: { label: 'Topic', value: '@topicId', @@ -65,6 +67,7 @@ interface SetDataPayload { label: 'Topic id', title: 'Hedera topic id', type: PropertyType.Input, + editable: true }, { name: 'active', @@ -72,6 +75,7 @@ interface SetDataPayload { title: 'Add this topic stream as active for new users', type: PropertyType.Checkbox, default: true, + editable: true }, { name: 'documentType', @@ -80,6 +84,7 @@ interface SetDataPayload { type: PropertyType.Select, items: GLOBAL_DOCUMENT_TYPE_ITEMS, default: GLOBAL_DOCUMENT_TYPE_DEFAULT, + editable: true }, ], }, @@ -98,6 +103,8 @@ export class GlobalEventsWriterBlock { user.userId, ); + const options = await ref.getOptions(user); + const existingTopicIds = new Set(); for (const stream of existingStreams) { if (stream?.globalTopicId) { @@ -105,7 +112,7 @@ export class GlobalEventsWriterBlock { } } - const optionTopicIds = ref.options.topicIds ?? []; + const optionTopicIds = options.topicIds ?? []; for (const optionTopic of optionTopicIds) { const optionTopicId = optionTopic.topicId; @@ -478,7 +485,9 @@ export class GlobalEventsWriterBlock { */ public async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - const config = ref.options || {} + const options = await ref.getOptions(user); + + const config = options || {} if (ref.dryRun) { return { @@ -507,7 +516,7 @@ export class GlobalEventsWriterBlock { const streams = await ref.databaseServer.getGlobalEventsWriterStreamsByUser(ref.policyId, ref.uuid, user.userId); const defaultTopicIds: string[] = []; - const optionTopicIds = ref.options.topicIds ?? []; + const optionTopicIds = options.topicIds ?? []; for (const item of optionTopicIds) { defaultTopicIds.push(item.topicId); @@ -535,6 +544,8 @@ export class GlobalEventsWriterBlock { */ public async setData(user: PolicyUser, data: SetDataPayload, _, actionStatus): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + const operation = data.operation; const streams = data.streams ?? []; @@ -610,7 +621,7 @@ export class GlobalEventsWriterBlock { } if (operation === 'Delete') { - const optionTopicIds = ref.options.topicIds ?? []; + const optionTopicIds = options.topicIds ?? []; const defaultTopicIds = new Set(); for (const item of optionTopicIds) { diff --git a/policy-service/src/policy-engine/blocks/group-manager.ts b/policy-service/src/policy-engine/blocks/group-manager.ts index 85d4545090..43b061426e 100644 --- a/policy-service/src/policy-engine/blocks/group-manager.ts +++ b/policy-service/src/policy-engine/blocks/group-manager.ts @@ -1,7 +1,7 @@ import { EventBlock } from '../helpers/decorators/index.js'; import { GroupAccessType, GroupRelationshipType, LocationType } from '@guardian/interfaces'; import { IPolicyGetData, IPolicyInterfaceBlock } from '../policy-engine.interface.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyInputEventType } from '../interfaces/index.js'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { PolicyUser } from '../policy-user.js'; @@ -30,7 +30,33 @@ import { RecordActionStep } from '../record-action-step.js'; PolicyInputEventType.RefreshEvent, ], output: null, - defaultEvent: false + defaultEvent: false, + properties: [ + { + name: 'canInvite', + label: 'Can Invite', + title: 'Can Invite', + type: PropertyType.Select, + default: 'owner', + items: [ + { label: 'Group Owner', value: 'owner'}, + { label: 'All', value: 'all'} + ], + editable: true + }, + { + name: 'canDelete', + label: 'Can Delete', + title: 'Can Delete', + type: PropertyType.Select, + default: 'owner', + items: [ + { label: 'Group Owner', value: 'owner'}, + { label: 'All', value: 'all'} + ], + editable: true + } + ] }, variables: [] }) @@ -49,6 +75,8 @@ export class GroupManagerBlock { role: string ): Promise { const group = await ref.databaseServer.getUserInGroup(ref.policyId, user.did, groupId); + const options = await ref.getOptions(user); + if (!group) { throw new Error(`Group not found`); } @@ -56,7 +84,7 @@ export class GroupManagerBlock { group.groupRelationshipType === GroupRelationshipType.Multiple && group.groupAccessType === GroupAccessType.Private ) { - if (ref.options.canInvite === 'all' || group.owner === user.did) { + if (options.canInvite === 'all' || group.owner === user.did) { const inviteId = await ref.databaseServer.createInviteToken(ref.policyId, group.uuid, user.did, role); return Buffer.from(JSON.stringify({ invitation: inviteId, @@ -92,6 +120,8 @@ export class GroupManagerBlock { if (user.did === did) { throw new Error(`Permission denied`); } + const options = await ref.getOptions(user); + const member = await ref.databaseServer.getUserInGroup(ref.policyId, did, groupId); if (!member) { throw new Error(`Group not found`); @@ -100,13 +130,13 @@ export class GroupManagerBlock { member.groupRelationshipType === GroupRelationshipType.Multiple && member.groupAccessType === GroupAccessType.Private ) { - if (ref.options.canDelete === 'all' || member.owner === user.did) { + if (options.canDelete === 'all' || member.owner === user.did) { await ref.databaseServer.deleteGroup(member); } else { throw new Error(`Permission denied`); } } else if (member.groupAccessType === GroupAccessType.Global) { - if (ref.options.canDelete === 'all' || member.owner === user.did) { + if (options.canDelete === 'all' || member.owner === user.did) { await ref.databaseServer.deleteGroup(member); } else { throw new Error(`Permission denied`); @@ -152,6 +182,7 @@ export class GroupManagerBlock { */ private async groupMapping(ref: IPolicyInterfaceBlock, user: PolicyUser, group: PolicyRoles): Promise { const config = PolicyUtils.getGroupTemplate(ref, group.groupName); + const options = await ref.getOptions(user); const members = (await ref.databaseServer.getAllMembersByGroup(group)).map(member => { return { did: member.did, @@ -161,8 +192,8 @@ export class GroupManagerBlock { current: member.did === user.did } }); - const canInvite = ref.options.canInvite === 'all' ? true : group.owner === user.did; - const canDelete = ref.options.canDelete === 'all' ? true : group.owner === user.did; + const canInvite = options.canInvite === 'all' ? true : group.owner === user.did; + const canDelete = options.canDelete === 'all' ? true : group.owner === user.did; return { id: group.uuid, role: group.role, diff --git a/policy-service/src/policy-engine/blocks/history-addon.ts b/policy-service/src/policy-engine/blocks/history-addon.ts index b21082055e..0e537d4df2 100644 --- a/policy-service/src/policy-engine/blocks/history-addon.ts +++ b/policy-service/src/policy-engine/blocks/history-addon.ts @@ -23,13 +23,15 @@ import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-abo label: 'Timeline Label Path', title: 'Timeline unit label path', type: PropertyType.Path, - default: '' + default: '', + editable: true }, { name: 'timelineDescriptionPath', label: 'Timeline Description Path', title: 'Timeline unit description', type: PropertyType.Path, - default: '' + default: '', + editable: true }] }, variables: [] diff --git a/policy-service/src/policy-engine/blocks/http-request-block.ts b/policy-service/src/policy-engine/blocks/http-request-block.ts index 835c2783f0..aab8ac9cb6 100644 --- a/policy-service/src/policy-engine/blocks/http-request-block.ts +++ b/policy-service/src/policy-engine/blocks/http-request-block.ts @@ -1,5 +1,5 @@ import { BasicBlock } from '../helpers/decorators/basic-block.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '../interfaces/index.js'; import { ActionCallback } from '../helpers/decorators/index.js'; import { CatchErrors } from '../helpers/decorators/catch-errors.js'; @@ -32,7 +32,67 @@ import { LocationType, WorkerTaskType } from '@guardian/interfaces'; PolicyOutputEventType.RefreshEvent, PolicyOutputEventType.ErrorEvent ], - defaultEvent: true + defaultEvent: true, + properties: [{ + name: 'url', + label: 'URL', + title: 'URL', + type: PropertyType.Input, + editable: true + },{ + name: 'body', + label: 'Body', + title: 'Body', + type: PropertyType.Select, + items: [], + editable: true + },{ + name: 'body', + label: 'Body', + title: 'Body', + type: PropertyType.Code, + editable: true + }, + { + name: 'headers', + label: 'Headers', + title: 'Headers', + type: PropertyType.Array, + editable: true, + items: { + label: 'Header', + value: '', + properties: [ + { + name: 'name', + label: 'Header Name', + title: 'Header Name', + type: PropertyType.Input, + editable: true + }, + { + name: 'value', + label: 'Header Value', + title: 'Header Value', + type: PropertyType.Input, + editable: true + }, + { + name: 'included', + label: 'Included value', + title: 'Included value', + type: PropertyType.Checkbox, + text: 'Include value in exported policy', + confirmation: { + title: 'title', + description: 'description', + condition: true + }, + editable: true + }, + ] + } + }] }, variables: [] }) @@ -144,6 +204,8 @@ export class HttpRequestBlock { const ref = PolicyComponentsUtils.GetBlockRef(this); event.data.data = event.data.data || {}; + const options = await ref.getOptions(event.user); + const variablesObj: any = { did: event?.user?.did, username: event?.user.username @@ -156,11 +218,11 @@ export class HttpRequestBlock { variablesObj.document = inputObject = (event?.data?.data as IPolicyDocument)?.document; } - const method = ref.options.method; - const url = this.replaceVariablesInString(ref.options.url, variablesObj); + const method = options.method; + const url = this.replaceVariablesInString(options.url, variablesObj); const headers = {}; - if (Array.isArray(ref.options.headers)) { - for (const header of ref.options.headers) { + if (Array.isArray(options.headers)) { + for (const header of options.headers) { headers[header.name] = this.replaceVariablesInString(header.value, variablesObj) } } diff --git a/policy-service/src/policy-engine/blocks/http-request-ui-addon.ts b/policy-service/src/policy-engine/blocks/http-request-ui-addon.ts index 97486eb2b4..8a2da3bd7a 100644 --- a/policy-service/src/policy-engine/blocks/http-request-ui-addon.ts +++ b/policy-service/src/policy-engine/blocks/http-request-ui-addon.ts @@ -28,6 +28,7 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; label: 'Method', title: 'Method', type: PropertyType.Select, + editable: true, items: [ { label: 'Get', @@ -50,13 +51,15 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; label: 'URL', title: 'URL', type: PropertyType.Input, - required: true + required: true, + editable: true }, { name: 'authentication', label: 'Authentication', title: 'Authentication', type: PropertyType.Select, + editable: true, items: [ { label: 'No Auth', @@ -75,7 +78,8 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; title: 'Authentication ClientId', type: PropertyType.Input, visible: 'authentication === "bearerToken"', - default: '' + default: '', + editable: true }, { name: 'authenticationURL', @@ -83,7 +87,8 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; title: 'Authentication Url', type: PropertyType.Input, visible: 'authentication === "bearerToken"', - default: '' + default: '', + editable: true }, { name: 'authenticationScopes', @@ -91,13 +96,15 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; title: 'Authentication Scopes', type: PropertyType.Input, visible: 'authentication === "bearerToken"', - default: '' + default: '', + editable: true }, { name: 'headers', label: 'Headers', title: 'Headers', type: PropertyType.Array, + editable: true, items: { label: 'Header', value: '', @@ -106,13 +113,15 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; name: 'name', label: 'Header name', title: 'Header name', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }, { name: 'value', label: 'Header value', title: 'Header value', - type: PropertyType.Input + type: PropertyType.Input, + editable: true } ] } @@ -129,7 +138,7 @@ export class HttpRequestUIAddon { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - const options = PolicyComponentsUtils.GetBlockUniqueOptionsObject(this); + const options = await ref.getOptions(user); return { id: ref.uuid, blockType: ref.blockType, diff --git a/policy-service/src/policy-engine/blocks/impact-addon.ts b/policy-service/src/policy-engine/blocks/impact-addon.ts index 5448ee9a41..52438d0186 100644 --- a/policy-service/src/policy-engine/blocks/impact-addon.ts +++ b/policy-service/src/policy-engine/blocks/impact-addon.ts @@ -30,6 +30,7 @@ import { BlockActionError } from '../errors/index.js'; label: 'Impact type', title: 'Impact type', type: PropertyType.Select, + editable: true, items: [{ label: 'Primary Impacts', value: 'Primary Impacts' @@ -43,22 +44,26 @@ import { BlockActionError } from '../errors/index.js'; name: 'label', label: 'Label', title: 'Label', + editable: true, type: PropertyType.Input }, { name: 'description', label: 'Description', title: 'Description', + editable: true, type: PropertyType.Input }, { name: 'amount', label: 'Amount (Formula)', title: 'Amount (Formula)', required: true, + editable: true, type: PropertyType.Input }, { name: 'unit', label: 'Unit', title: 'Unit', + editable: true, type: PropertyType.Input }] }, @@ -99,23 +104,24 @@ export class TokenOperationAddon { userId: string | null ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const policySchema = await this.getSchema(); - const amount = PolicyUtils.aggregate(ref.options.amount, documents); + const amount = PolicyUtils.aggregate(options.amount, documents); const vcHelper = new VcHelper(); const vcSubject: any = { ...SchemaHelper.getContext(policySchema), - impactType: ref.options.impactType === 'Primary Impacts' ? 'Primary Impacts' : 'Secondary Impacts', + impactType: options.impactType === 'Primary Impacts' ? 'Primary Impacts' : 'Secondary Impacts', date: (new Date()).toISOString(), amount: amount.toString(), } - if (ref.options.unit) { - vcSubject.unit = ref.options.unit; + if (options.unit) { + vcSubject.unit = options.unit; } - if (ref.options.label) { - vcSubject.label = ref.options.label; + if (options.label) { + vcSubject.label = options.label; } - if (ref.options.description) { - vcSubject.description = ref.options.description; + if (options.description) { + vcSubject.description = options.description; } const didDocument = await root.loadDidDocument(ref, userId); const uuid = await ref.components.generateUUID(); diff --git a/policy-service/src/policy-engine/blocks/information-block.ts b/policy-service/src/policy-engine/blocks/information-block.ts index 9b1c5dccd1..d280aff8aa 100644 --- a/policy-service/src/policy-engine/blocks/information-block.ts +++ b/policy-service/src/policy-engine/blocks/information-block.ts @@ -1,6 +1,6 @@ import { DataSourceBlock } from '../helpers/decorators/data-source-block.js'; import { PolicyInputEventType } from '../interfaces/index.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { PolicyUser } from '../policy-user.js'; import { LocationType } from '@guardian/interfaces'; @@ -25,7 +25,38 @@ import { IPolicyGetData } from '@policy-engine/policy-engine.interface.js'; PolicyInputEventType.RefreshEvent, ], output: null, - defaultEvent: false + defaultEvent: false, + properties: [{ + name: 'uiMetaData', + label: 'UI', + title: 'UI Properties', + type: PropertyType.Group, + editable: true, + properties: [{ + name: 'title', + label: 'Title', + title: 'Title', + type: PropertyType.Input, + editable: true + }, + { + name: 'description', + label: 'Description', + title: 'Description', + type: PropertyType.Input, + editable: true + },{ + name: 'type', + label: 'Type', + title: 'Type', + type: PropertyType.Select, + items: [ + { label: 'LOADER', value: 'loader'}, + { label: 'TEXT', value: 'text'} + ], + editable: true, + }] + }] }, variables: [] }) @@ -36,6 +67,8 @@ export class InformationBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + return { id: ref.uuid, blockType: ref.blockType, @@ -44,7 +77,7 @@ export class InformationBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - uiMetaData: ref.options?.uiMetaData + uiMetaData: options?.uiMetaData }; } } diff --git a/policy-service/src/policy-engine/blocks/integration-button-block.ts b/policy-service/src/policy-engine/blocks/integration-button-block.ts index 484fd643d9..c83509ff37 100644 --- a/policy-service/src/policy-engine/blocks/integration-button-block.ts +++ b/policy-service/src/policy-engine/blocks/integration-button-block.ts @@ -9,7 +9,6 @@ import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfac import { generateConfigForIntegrationBlock, VcHelper, IntegrationServiceFactory, HederaDidDocument, VcDocumentDefinition, VcDocument } from '@guardian/common'; import { PolicyUtils } from '../helpers/utils.js'; import { FilterQuery } from '@mikro-orm/core'; - /** * Document action clock with UI */ @@ -27,6 +26,8 @@ export class IntegrationButtonBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -35,10 +36,10 @@ export class IntegrationButtonBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - integrationType: ref.options.integrationType || '', - requestName: ref.options.requestName || '', - buttonName: ref.options.buttonName, - hideWhenDiscontinued: !!ref.options.hideWhenDiscontinued, + integrationType: options.integrationType || '', + requestName: options.requestName || '', + buttonName: options.buttonName, + hideWhenDiscontinued: !!options.hideWhenDiscontinued, } return data; } @@ -59,11 +60,12 @@ export class IntegrationButtonBlock { tag: any }, _, actionStatus): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - const requestNameSplited = ref.options.requestName.split('_'); + const options = await ref.getOptions(user); + const requestNameSplited = options.requestName.split('_'); const params = {}; - Object.entries(ref.options.requestParams).forEach(([key, value]: [string, string]) => { + Object.entries(options.requestParams).forEach(([key, value]: [string, string]) => { if (key.startsWith('path_')) { const keyName = key.split('path_')[1]; @@ -82,14 +84,14 @@ export class IntegrationButtonBlock { const methodName = requestNameSplited[requestNameSplited.length - 1]; const dataForRequest = IntegrationServiceFactory.getDataForRequest( - ref.options.integrationType, - IntegrationServiceFactory.getAvailableMethods(ref.options.integrationType)[methodName], + options.integrationType, + IntegrationServiceFactory.getAvailableMethods(options.integrationType)[methodName], params ); const dataForRequestStr = JSON.stringify(dataForRequest); - if (ref.options.getFromCache) { + if (options.getFromCache) { const cachedData = await ref.databaseServer.getVcDocument({ 'option.requestParams': dataForRequestStr, policyId: ref.policyId, @@ -111,7 +113,7 @@ export class IntegrationButtonBlock { } } - const integrationService = IntegrationServiceFactory.create(ref.options.integrationType); + const integrationService = IntegrationServiceFactory.create(options.integrationType); const { data: responseFromRequest, diff --git a/policy-service/src/policy-engine/blocks/math-block.ts b/policy-service/src/policy-engine/blocks/math-block.ts index 53b8cbecde..f1175b7f9f 100644 --- a/policy-service/src/policy-engine/blocks/math-block.ts +++ b/policy-service/src/policy-engine/blocks/math-block.ts @@ -54,17 +54,20 @@ interface IMetadata { name: 'inputSchema', label: 'Input Schema', title: 'Input Schema', - type: PropertyType.Schemas + type: PropertyType.Schemas, + editable: false }, { name: 'outputSchema', label: 'Output Schema', title: 'Output Schema', - type: PropertyType.Schemas + type: PropertyType.Schemas, + editable: false }, { name: 'unsigned', label: 'Unsigned VC', title: 'Unsigned document', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }] }, variables: [ @@ -110,11 +113,12 @@ export class MathBlock { documents: DocumentMap, user: PolicyUser ): Promise { - const outputSchema = await PolicyUtils.loadSchemaByID(ref, ref.options.outputSchema); + const options = await ref.getOptions(user); + const outputSchema = await PolicyUtils.loadSchemaByID(ref, options.outputSchema); const schema = new Schema(outputSchema); // Artifacts - const files = Array.isArray(ref.options.artifacts) ? ref.options.artifacts : []; + const files = Array.isArray(options.artifacts) ? options.artifacts : []; const artifacts = []; const jsonArtifacts = files.filter((file: any) => file.type === ArtifactType.JSON); for (const jsonArtifact of jsonArtifacts) { @@ -124,12 +128,12 @@ export class MathBlock { // Run const result = await this.createWorker({ - expression: ref.options.expression, + expression: options.expression, documents: documents.toJson(), artifacts, user, schema, - copy: !ref.options.outputSchema || ref.options.outputSchema === ref.options.inputSchema + copy: !options.outputSchema || options.outputSchema === options.inputSchema }) return result; @@ -154,12 +158,15 @@ export class MathBlock { private async process( documents: IPolicyDocument, ref: IPolicyCalculateBlock, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { if (!documents) { throw new BlockActionError('Invalid VC', ref.blockType, ref.uuid); } + const options = await ref.getOptions(user); + const sources: IPolicyDocument[] = await PolicyUtils.findRelationships(ref, documents); const context = await ref.debugContext({ documents, sources }); @@ -173,11 +180,11 @@ export class MathBlock { map.addRelationships(contextRelationships.map((d) => this.getCredentialSubject(d))); const newJson = await this.calculate(ref, map, docOwner); - if (ref.options.unsigned) { + if (options.unsigned) { return await this.createUnsignedDocument(newJson, ref); } else { const metadata = await this.aggregateMetadata(contextDocument, ref, userId); - return await this.createDocument(newJson, metadata, ref, userId); + return await this.createDocument(newJson, metadata, ref, userId, user); } } @@ -199,6 +206,7 @@ export class MathBlock { let tokens: any = {}; let id: string; let reference: string; + if (isArray) { const credentialSubject = documents[0].document?.credentialSubject; if (credentialSubject) { @@ -249,7 +257,8 @@ export class MathBlock { json: any, metadata: IMetadata, ref: IPolicyCalculateBlock, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { const { owner, @@ -263,7 +272,9 @@ export class MathBlock { // <-- new vc const VCHelper = new VcHelper(); - const outputSchema = await PolicyUtils.loadSchemaByID(ref, ref.options.outputSchema); + const options = await ref.getOptions(user); + + const outputSchema = await PolicyUtils.loadSchemaByID(ref, options.outputSchema); const vcSubject: any = { ...SchemaHelper.getContext(outputSchema), @@ -339,12 +350,12 @@ export class MathBlock { if (Array.isArray(event.data.data)) { const result: IPolicyDocument[] = []; for (const doc of event.data.data) { - const newVC = await this.process(doc, ref, event?.user?.userId); + const newVC = await this.process(doc, ref, event?.user?.userId, event.user); result.push(newVC) } event.data.data = result; } else { - event.data.data = await this.process(event.data.data, ref, event?.user?.userId); + event.data.data = await this.process(event.data.data, ref, event?.user?.userId, event.user); } ref.triggerEvents(PolicyOutputEventType.RunEvent, event.user, event.data, event.actionStatus); diff --git a/policy-service/src/policy-engine/blocks/mint-block.ts b/policy-service/src/policy-engine/blocks/mint-block.ts index c200e652a0..5a0e8a0d32 100644 --- a/policy-service/src/policy-engine/blocks/mint-block.ts +++ b/policy-service/src/policy-engine/blocks/mint-block.ts @@ -73,15 +73,16 @@ export class MintBlock { * @param docs * @private */ - private async getToken(ref: AnyBlockType, docs: IPolicyDocument[]): Promise { + private async getToken(ref: AnyBlockType, docs: IPolicyDocument[], user?: PolicyUser): Promise { let token: TokenCollection; - if (ref.options.useTemplate) { + const options = await ref.getOptions(user); + if (options.useTemplate) { if (docs[0].tokens) { - const tokenId = docs[0].tokens[ref.options.template]; + const tokenId = docs[0].tokens[options.template]; token = await ref.databaseServer.getToken(tokenId, ref.dryRun); } } else { - token = await ref.databaseServer.getToken(ref.options.tokenId); + token = await ref.databaseServer.getToken(options.tokenId); } if (!token) { throw new BlockActionError('Bad token id', ref.blockType, ref.uuid); @@ -95,11 +96,12 @@ export class MintBlock { * @param docs * @private */ - private getObjects(ref: AnyBlockType, docs: IPolicyDocument[]): any { + private async getObjects(ref: AnyBlockType, docs: IPolicyDocument[], user?: PolicyUser): Promise { const vcs: VcDocument[] = []; const messages: string[] = []; const topics: string[] = []; - const field = ref.options.accountId || 'default'; + const options = await ref.getOptions(user); + const field = options.accountId || 'default'; const accounts: string[] = []; for (const doc of docs) { if (doc.signature === DocumentSignature.INVALID) { @@ -146,20 +148,22 @@ export class MintBlock { * @param userId * @private */ - private getAccount( + private async getAccount( ref: AnyBlockType, docs: IPolicyDocument[], accounts: string[], relayerAccount: string, - userId: string | null - ): string { + userId: string | null, + user?: PolicyUser + ): Promise { let targetAccount: string; - if (ref.options.accountType !== 'custom-value') { + const options = await ref.getOptions(user); + if (options.accountType !== 'custom-value') { const firstAccounts = accounts[0]; if (accounts.find(a => a !== firstAccounts)) { ref.error(`More than one account found! Transfer made on the first (${firstAccounts})`); } - if (ref.options.accountId) { + if (options.accountId) { targetAccount = firstAccounts; } else { targetAccount = relayerAccount; @@ -168,7 +172,7 @@ export class MintBlock { throw new BlockActionError('Token recipient is not set', ref.blockType, ref.uuid); } } else { - targetAccount = ref.options.accountIdValue; + targetAccount = options.accountIdValue; } return targetAccount; } @@ -309,11 +313,11 @@ export class MintBlock { actionStatus: RecordActionStep ): Promise<[IPolicyDocument, number]> { const ref = PolicyComponentsUtils.GetBlockRef(this); - + const options = await ref.getOptions(user); const tags = await PolicyUtils.getBlockTags(ref); const uuid: string = await ref.components.generateUUID(); - const amount = PolicyUtils.aggregate(ref.options.rule, documents); + const amount = PolicyUtils.aggregate(options.rule, documents); if (Number.isNaN(amount) || !Number.isFinite(amount) || amount < 0) { throw new BlockActionError(`Invalid token value: ${amount}`, ref.blockType, ref.uuid); } @@ -411,7 +415,7 @@ export class MintBlock { const savedVp = await ref.databaseServer.saveVP(vpDocument); // #endregion - const transactionMemo = `${vpMessageId} ${MessageMemo.parseMemo(true, ref.options.memo, savedVp)}`.trimEnd(); + const transactionMemo = `${vpMessageId} ${MessageMemo.parseMemo(true, options.memo, savedVp)}`.trimEnd(); await MintService.mint({ ref, token, @@ -528,13 +532,13 @@ export class MintBlock { additionalDocs: IPolicyDocument[], userId: string | null ) { - const token = await this.getToken(ref, docs); - const { vcs, messages, topics, accounts } = this.getObjects(ref, docs); + const token = await this.getToken(ref, docs, user); + const { vcs, messages, topics, accounts } = await this.getObjects(ref, docs, event.user); const additionalMessages = this.getAdditionalMessages(additionalDocs); const topicId = topics[0]; const relayerAccount = await PolicyUtils.getDocumentRelayerAccount(ref, docs[0], userId); - const targetAccount = this.getAccount(ref, docs, accounts, relayerAccount, userId); + const targetAccount = await this.getAccount(ref, docs, accounts, relayerAccount, userId, event.user); const [vp, amount] = await this.mintProcessing( token, diff --git a/policy-service/src/policy-engine/blocks/module.ts b/policy-service/src/policy-engine/blocks/module.ts index 043250334e..e5fad1e55e 100644 --- a/policy-service/src/policy-engine/blocks/module.ts +++ b/policy-service/src/policy-engine/blocks/module.ts @@ -100,6 +100,7 @@ export class ModuleBlock { */ private _getVariable(name: any, type: string): any { const ref = PolicyComponentsUtils.GetBlockRef(this); + if (Array.isArray(ref.options.variables)) { for (const variable of ref.options.variables) { if (type) { diff --git a/policy-service/src/policy-engine/blocks/multi-sign-block.ts b/policy-service/src/policy-engine/blocks/multi-sign-block.ts index 6f1b561dca..b09027c668 100644 --- a/policy-service/src/policy-engine/blocks/multi-sign-block.ts +++ b/policy-service/src/policy-engine/blocks/multi-sign-block.ts @@ -50,7 +50,8 @@ enum DocumentStatus { 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' + default: '50', + editable: false }] }, variables: [] @@ -108,6 +109,7 @@ export class MultiSignBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -130,6 +132,7 @@ export class MultiSignBlock { */ async setData(user: PolicyUser, blockData: any, _, actionStatus): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const { status, document } = blockData; const documentId = document.id; const sourceDoc = await ref.databaseServer.getVcDocument(documentId); @@ -207,9 +210,10 @@ export class MultiSignBlock { documentId: string, currentUser: PolicyUser, userId: string | null, - actionStatus: RecordActionStep + actionStatus: RecordActionStep, ) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const data = await ref.databaseServer.getMultiSignDocuments(ref.uuid, documentId, currentUser.group); let signed = 0; @@ -314,6 +318,7 @@ export class MultiSignBlock { */ private async getDocumentStatus(document: IPolicyDocument, user: PolicyUser) { 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); diff --git a/policy-service/src/policy-engine/blocks/notification.block.ts b/policy-service/src/policy-engine/blocks/notification.block.ts index 2fe80d67a0..63c92b97a8 100644 --- a/policy-service/src/policy-engine/blocks/notification.block.ts +++ b/policy-service/src/policy-engine/blocks/notification.block.ts @@ -28,6 +28,7 @@ import { NotificationType, UserOption, } from '@guardian/interfaces'; +import { PolicyUser } from '@policy-engine/policy-user.js'; /** * Notification block @@ -52,6 +53,7 @@ import { title: 'Title', type: PropertyType.Input, required: true, + editable: true }, { name: 'message', @@ -59,12 +61,14 @@ import { title: 'Message', type: PropertyType.Input, required: true, + editable: true }, { name: 'type', label: 'Type', title: 'Type', type: PropertyType.Select, + editable: true, items: [ { label: 'Info', @@ -90,12 +94,14 @@ import { label: 'Link notification to policy', title: 'Link notification to policy', type: PropertyType.Checkbox, + editable: true }, { name: 'user', label: 'User', title: 'User', type: PropertyType.Select, + editable: true, items: [ { label: 'All', @@ -136,6 +142,7 @@ import { items: SelectItemType.Roles, visible: 'user === "ROLE"', required: true, + editable: true }, { name: 'grouped', @@ -143,6 +150,7 @@ import { title: 'Only for current user group', type: PropertyType.Checkbox, visible: 'user === "ROLE"', + editable: true }, ], }, @@ -153,11 +161,14 @@ export class NotificationBlock { * @param ref Block ref * @returns Function */ - private getNotificationFunction( - ref: any - ): (title: string, message: string, userId: string) => void { + private async getNotificationFunction( + ref: any, + user: PolicyUser + ): Promise<(title: string, message: string, userId: string) => void> { let fn; - switch (ref.options.type) { + const options = await ref.getOptions(user); + + switch (options.type) { case NotificationType.INFO: fn = NotificationHelper.info; break; @@ -175,7 +186,7 @@ export class NotificationBlock { } let notify = fn; - if (ref.options.link) { + if (options.link) { notify = async (title: string, message: string, userId: string) => await fn( title, @@ -199,9 +210,11 @@ export class NotificationBlock { const ref = PolicyComponentsUtils.GetBlockRef(this); - const notify = this.getNotificationFunction(ref); + const options = await ref.getOptions(event.user); + + const notify = await this.getNotificationFunction(ref, event.user); - switch (ref.options.user) { + switch (options.user) { case UserOption.ALL: { if (!ref.dryRun) { const policyUsers = @@ -213,8 +226,8 @@ export class NotificationBlock { ); for (const user of users) { await notify( - ref.options.title, - ref.options.message, + options.title, + options.message, user.id ); } @@ -224,8 +237,8 @@ export class NotificationBlock { if (event.user.did !== ref.policyOwner && !ref.dryRun) { const user = await PolicyUtils.getUser(ref, event.user.did, event?.user?.userId); await notify( - ref.options.title, - ref.options.message, + options.title, + options.message, user.id ); break; @@ -237,7 +250,7 @@ export class NotificationBlock { case UserOption.ALL: case UserOption.POLICY_OWNER: { const owner = await new Users().getUserById(ref.policyOwner, event?.user?.userId); - await notify(ref.options.title, ref.options.message, owner.id); + await notify(options.title, options.message, owner.id); break; } case UserOption.DOCUMENT_OWNER: { @@ -250,8 +263,8 @@ export class NotificationBlock { ); if (user.did === ref.policyOwner || !ref.dryRun) { await notify( - ref.options.title, - ref.options.message, + options.title, + options.message, user.id ); } @@ -267,8 +280,8 @@ export class NotificationBlock { ); if (user.did === ref.policyOwner || !ref.dryRun) { await notify( - ref.options.title, - ref.options.message, + options.title, + options.message, user.id ); } @@ -283,8 +296,8 @@ export class NotificationBlock { const owner = await PolicyUtils.getUser(ref, role.owner, event?.user?.userId); if (owner.did === ref.policyOwner || !ref.dryRun) { await notify( - ref.options.title, - ref.options.message, + options.title, + options.message, owner.id ); } @@ -292,15 +305,15 @@ export class NotificationBlock { break; } case UserOption.ROLE: { - let policyUsers = ref.options.grouped + let policyUsers = options.grouped ? await ref.databaseServer.getAllUsersByRole( ref.policyId, event.user.group, - ref.options.role + options.role ) : await ref.databaseServer.getUsersByRole( ref.policyId, - ref.options.role + options.role ); policyUsers = ref.dryRun ? policyUsers.filter((pu) => pu.did === ref.policyOwner) @@ -310,8 +323,8 @@ export class NotificationBlock { ); for (const user of users) { await notify( - ref.options.title, - ref.options.message, + options.title, + options.message, user.id ); } diff --git a/policy-service/src/policy-engine/blocks/policy-roles.ts b/policy-service/src/policy-engine/blocks/policy-roles.ts index cece849afd..60682a11ba 100644 --- a/policy-service/src/policy-engine/blocks/policy-roles.ts +++ b/policy-service/src/policy-engine/blocks/policy-roles.ts @@ -371,8 +371,10 @@ export class PolicyRolesBlock { */ async getData(user: PolicyUser): 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 options = await ref.getOptions(user); + + const roles: string[] = Array.isArray(options.roles) ? options.roles : []; + const groups: string[] = Array.isArray(options.groups) ? options.groups : []; const policyGroups = PolicyUtils.getGroupTemplates(ref); const groupMap = {}; for (const item of policyGroups) { @@ -395,7 +397,7 @@ export class PolicyRolesBlock { groups, groupMap, isMultipleGroups: policyGroups.length > 0, - uiMetaData: ref.options.uiMetaData + uiMetaData: options.uiMetaData } } diff --git a/policy-service/src/policy-engine/blocks/reassigning.block.ts b/policy-service/src/policy-engine/blocks/reassigning.block.ts index 076ae67785..96fd2a0c23 100644 --- a/policy-service/src/policy-engine/blocks/reassigning.block.ts +++ b/policy-service/src/policy-engine/blocks/reassigning.block.ts @@ -4,7 +4,7 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { AnyBlockType, IPolicyBlock, IPolicyDocument, IPolicyEventState } from '../policy-engine.interface.js'; import { CatchErrors } from '../helpers/decorators/catch-errors.js'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '../interfaces/index.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyUser } from '../policy-user.js'; import { PolicyUtils } from '../helpers/utils.js'; import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfaces/external-event.js'; @@ -35,7 +35,32 @@ import { RecordActionStep } from '../record-action-step.js'; PolicyOutputEventType.RefreshEvent, PolicyOutputEventType.ErrorEvent ], - defaultEvent: true + defaultEvent: true, + properties: [ + { + name: 'issuer', + label: 'Issuer', + title: 'Issuer', + type: PropertyType.Select, + editable: true, + items: [ + { label: 'Current User', value: ''}, + { label: 'Document Owner', value: 'owner'}, + { label: 'Policy Owner', value: 'policyOwner'} + ] + }, + { + name: 'actor', + label: 'Actor', + title: 'Actor', + type: PropertyType.Select, + editable: true, + items: [ + { label: 'Current User', value: ''}, + { label: 'Document Owner', value: 'owner'}, + { label: 'Document Issuer', value: 'issuer'} + ] + }] }, variables: [] }) @@ -64,16 +89,17 @@ export class ReassigningBlock { actor: PolicyUser }> { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const owner: PolicyUser = await PolicyUtils.getDocumentOwner(ref, document, userId); const relayerAccount = await PolicyUtils.getDocumentRelayerAccount(ref, document, user.userId); let groupContext: any; let issuer: string; - if (ref.options.issuer === 'owner') { + if (options.issuer === 'owner') { const cred = await PolicyUtils.getUserCredentials(ref, document.owner, userId); issuer = cred.did; groupContext = await PolicyUtils.getGroupContext(ref, owner); - } else if (ref.options.issuer === 'policyOwner') { + } else if (options.issuer === 'policyOwner') { const cred = await PolicyUtils.getUserCredentials(ref, ref.policyOwner, userId); issuer = cred.did; groupContext = null; @@ -84,9 +110,9 @@ export class ReassigningBlock { } let actor: PolicyUser; - if (ref.options.actor === 'owner') { + if (options.actor === 'owner') { actor = await PolicyUtils.getDocumentOwner(ref, document, userId); - } else if (ref.options.actor === 'issuer') { + } else if (options.actor === 'issuer') { actor = await PolicyUtils.getPolicyUser(ref, issuer, document.group, userId); } else { actor = user; diff --git a/policy-service/src/policy-engine/blocks/report-block.ts b/policy-service/src/policy-engine/blocks/report-block.ts index 7ce4f2816f..4bfc534d1a 100644 --- a/policy-service/src/policy-engine/blocks/report-block.ts +++ b/policy-service/src/policy-engine/blocks/report-block.ts @@ -37,11 +37,13 @@ import { FilterObject } from '@mikro-orm/core'; label: 'UI', title: 'UI Properties', type: PropertyType.Group, + editable: true, properties: [{ name: 'vpSectionHeader', label: 'VP section header', title: 'VP section header', - type: PropertyType.Input + type: PropertyType.Input, + editable: true } ] }] @@ -434,6 +436,8 @@ export class ReportBlock { */ async getData(user: PolicyUser, uuid: string): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + try { const blockState = this.state[user.id] || {}; if (!blockState.lastValue) { @@ -446,7 +450,7 @@ export class ReportBlock { user.location === LocationType.REMOTE ), hash: null, - uiMetaData: ref.options.uiMetaData, + uiMetaData: options.uiMetaData, data: null }; } @@ -505,7 +509,7 @@ export class ReportBlock { for (const reportItem of reportItems) { const [documentsNotFound] = await reportItem.run( documents, - variables + variables, ); if (documentsNotFound) { break; @@ -528,7 +532,7 @@ export class ReportBlock { user.location === LocationType.REMOTE ), hash, - uiMetaData: ref.options.uiMetaData, + uiMetaData: options.uiMetaData, data: report }; } catch (error) { diff --git a/policy-service/src/policy-engine/blocks/report-item-block.ts b/policy-service/src/policy-engine/blocks/report-item-block.ts index 6c37347054..ba0b356994 100644 --- a/policy-service/src/policy-engine/blocks/report-item-block.ts +++ b/policy-service/src/policy-engine/blocks/report-item-block.ts @@ -58,20 +58,22 @@ export class ReportItemBlock { */ public async run( resultFields: IReportItem[], - variables: any + variables: any, ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - const icon = ref.options.icon; - const title = ref.options.title; - const description = ref.options.description; - const visible = ref.options.visible; - const iconType = ref.options.iconType; - const multiple = ref.options.multiple; - const dynamicFilters = ref.options.dynamicFilters; + const options = await ref.getOptions(); + + const icon = options.icon; + const title = options.title; + const description = options.description; + const visible = options.visible; + const iconType = options.iconType; + const multiple = options.multiple; + const dynamicFilters = options.dynamicFilters; const filtersToVc: any = {}; - if (ref.options.filters) { - for (const filter of ref.options.filters) { + if (options.filters) { + for (const filter of options.filters) { let expr: any; if (filter.typeValue === 'value') { expr = filter.value; @@ -149,8 +151,8 @@ export class ReportItemBlock { item.document = vcDocument; } - if (ref.options.variables) { - for (const variable of ref.options.variables) { + if (options.variables) { + for (const variable of options.variables) { const findOptionsResult = findOptions(vcDocument, variable.value); if (multiple) { variables[variable.name] = variables[variable.name] || [] diff --git a/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts b/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts index 7dd1e71290..b3b10b9249 100644 --- a/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts +++ b/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts @@ -140,6 +140,8 @@ export class RequestVcDocumentBlockAddon { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); + const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -148,7 +150,7 @@ export class RequestVcDocumentBlockAddon { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - ...ref.options, + ...options, schema: { ...this._schema, fields: [], conditions: [] }, }; return data; @@ -185,6 +187,7 @@ export class RequestVcDocumentBlockAddon { ); } const document = _data.document; + const options = await ref.getOptions(user); const disposeTables = await hydrateTablesInObject( document, @@ -198,7 +201,8 @@ export class RequestVcDocumentBlockAddon { const presetCheck = await this.checkPreset( ref, document, - documentRef + documentRef, + user ); if (!presetCheck.valid) { throw new BlockActionError( @@ -211,8 +215,8 @@ export class RequestVcDocumentBlockAddon { SchemaHelper.updateObjectContext(this._schema, document); const _vcHelper = new VcHelper(); - const idType = ref.options.idType; - const forceRelayerAccount = ref.options.forceRelayerAccount; + const idType = options.idType; + const forceRelayerAccount = options.forceRelayerAccount; const inheritRelayerAccount = PolicyComponentsUtils.IsInheritRelayerAccount(ref.policyId, forceRelayerAccount); //Relayer Account @@ -273,7 +277,7 @@ export class RequestVcDocumentBlockAddon { PolicyUtils.setDocumentTags(item, tags); const accounts = PolicyUtils.getHederaAccounts(vc, relayerAccount, this._schema); - const schemaIRI = ref.options.schema; + const schemaIRI = options.schema; item.type = schemaIRI; item.schema = schemaIRI; item.accounts = accounts; @@ -303,14 +307,17 @@ export class RequestVcDocumentBlockAddon { private async checkPreset( ref: AnyBlockType, document: any, - documentRef: VcDocumentCollection + documentRef: VcDocumentCollection, + user?: PolicyUser ): Promise { + const options = await ref.getOptions(user); + if ( - ref.options.presetFields && - ref.options.presetFields.length && - ref.options.presetSchema + options.presetFields && + options.presetFields.length && + options.presetSchema ) { - const readonly = ref.options.presetFields.filter( + const readonly = options.presetFields.filter( (item: any) => item.readonly && item.value ); if (!readonly.length || !document || !documentRef) { diff --git a/policy-service/src/policy-engine/blocks/request-vc-document-block.ts b/policy-service/src/policy-engine/blocks/request-vc-document-block.ts index b7499a7e31..ef183ac64b 100644 --- a/policy-service/src/policy-engine/blocks/request-vc-document-block.ts +++ b/policy-service/src/policy-engine/blocks/request-vc-document-block.ts @@ -193,13 +193,14 @@ export class RequestVcDocumentBlock { private async setBlockData(user: PolicyUser, data: IPolicyDocument, actionStatus: RecordActionStep) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); try { //Prepare data const document = await this.prepareDocument(data); const draft = data.draft; const draftId = data.draftId; - const editType = ref.options.editType; - const forceRelayerAccount = ref.options.forceRelayerAccount; + const editType = options.editType; + const forceRelayerAccount = options.forceRelayerAccount; const inheritRelayerAccount = PolicyComponentsUtils.IsInheritRelayerAccount(ref.policyId, forceRelayerAccount); const documentRef = await this.getRelationships(ref, data.ref); @@ -220,7 +221,7 @@ export class RequestVcDocumentBlock { } //Validate preset - const presetCheck = await this.checkPreset(ref, document, documentRef) + const presetCheck = await this.checkPreset(ref, document, documentRef, user); if (!presetCheck.valid) { throw new BlockActionError( JSON.stringify(presetCheck.error), @@ -325,14 +326,17 @@ export class RequestVcDocumentBlock { private async checkPreset( ref: AnyBlockType, document: any, - documentRef: VcDocumentCollection + documentRef: VcDocumentCollection, + user?: PolicyUser ): Promise { + const options = await ref.getOptions(user); + if ( - ref.options.presetFields && - ref.options.presetFields.length && - ref.options.presetSchema + options.presetFields && + options.presetFields.length && + options.presetSchema ) { - const readonly = ref.options.presetFields.filter( + const readonly = options.presetFields.filter( (item: any) => item.readonly && item.value ); if (!readonly.length || !document || !documentRef) { @@ -398,11 +402,11 @@ export class RequestVcDocumentBlock { actionStatusId: string, ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - + const options = await ref.getOptions(user); SchemaHelper.updateObjectContext(this._schema, document); const _vcHelper = new VcHelper(); - const idType = ref.options.idType; + const idType = options.idType; const credentialSubject = document; credentialSubject.policyId = ref.policyId; diff --git a/policy-service/src/policy-engine/blocks/retirement-block.ts b/policy-service/src/policy-engine/blocks/retirement-block.ts index 56730ec67a..5d1e137301 100644 --- a/policy-service/src/policy-engine/blocks/retirement-block.ts +++ b/policy-service/src/policy-engine/blocks/retirement-block.ts @@ -142,6 +142,7 @@ export class RetirementBlock { actionStatus: RecordActionStep ): Promise<[IPolicyDocument, number]> { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const tags = await PolicyUtils.getBlockTags(ref); @@ -155,7 +156,7 @@ export class RetirementBlock { let tokenValue: number = 0; let tokenAmount: string = '0'; if (token.tokenType === TokenType.NON_FUNGIBLE) { - const exprOpt = ref.options.serialNumbersExpression; + const exprOpt = options.serialNumbersExpression; if (!exprOpt || !String(exprOpt).trim()) { throw new Error('For NON_FUNGIBLE tokens, Serial numbers is required'); } @@ -207,15 +208,16 @@ export class RetirementBlock { } } else if (token.tokenType === TokenType.FUNGIBLE) { - const ruleOpt = ref.options.rule + const ruleOpt = options.rule const hasRule = ruleOpt !== null && ruleOpt !== undefined && (typeof ruleOpt !== 'string' || ruleOpt.trim() !== ''); if (!hasRule) { throw new Error('For FUNGIBLE tokens, Rule is required'); } - const amount = PolicyUtils.aggregate(ref.options.rule, documents); - [tokenValue, tokenAmount] = PolicyUtils.tokenAmount(token, amount, ref.options.roundMethod); + + const amount = PolicyUtils.aggregate(options.rule, documents); + [tokenValue, tokenAmount] = PolicyUtils.tokenAmount(token, amount, options.roundMethod); } const wipeVC = await this.createWipeVC(policyOwnerDidDocument, token, tokenAmount, ref, serialNumbers, actionStatus?.id); @@ -311,15 +313,17 @@ export class RetirementBlock { * @param docs * @private */ - private async getToken(ref: AnyBlockType, docs: IPolicyDocument[]): Promise { + private async getToken(ref: AnyBlockType, docs: IPolicyDocument[], user?: PolicyUser): Promise { let token: TokenCollection; - if (ref.options.useTemplate) { + const options = await ref.getOptions(user); + + if (options.useTemplate) { if (docs[0].tokens) { - const tokenId = docs[0].tokens[ref.options.template]; + const tokenId = docs[0].tokens[options.template]; token = await ref.databaseServer.getToken(tokenId, ref.dryRun); } } else { - token = await ref.databaseServer.getToken(ref.options.tokenId); + token = await ref.databaseServer.getToken(options.tokenId); } if (!token) { throw new BlockActionError('Bad token id', ref.blockType, ref.uuid); @@ -343,12 +347,14 @@ export class RetirementBlock { async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); + const docs = PolicyUtils.getArray(event.data.data); if (!docs.length && docs[0]) { throw new BlockActionError('Bad VC', ref.blockType, ref.uuid); } - const token = await this.getToken(ref, docs); + const token = await this.getToken(ref, docs, event.user); if (!token) { throw new BlockActionError('Bad token id', ref.blockType, ref.uuid); } @@ -361,7 +367,7 @@ export class RetirementBlock { const vcs: VcDocument[] = []; const vsMessages: string[] = []; const topicIds: string[] = []; - const field = ref.options.accountId || 'default'; + const field = options.accountId || 'default'; const accounts: string[] = []; for (const doc of docs) { if (doc.signature === DocumentSignature.INVALID) { @@ -389,7 +395,7 @@ export class RetirementBlock { const relayerAccount = await PolicyUtils.getDocumentRelayerAccount(ref, docs[0], event?.user?.userId); let targetAccount: string; - if (ref.options.accountId) { + if (options.accountId) { targetAccount = firstAccounts; } else { targetAccount = relayerAccount; diff --git a/policy-service/src/policy-engine/blocks/revocation-block.ts b/policy-service/src/policy-engine/blocks/revocation-block.ts index 6bc542a9ab..edf581f906 100644 --- a/policy-service/src/policy-engine/blocks/revocation-block.ts +++ b/policy-service/src/policy-engine/blocks/revocation-block.ts @@ -40,6 +40,7 @@ export const RevokedStatus = 'Revoked'; title: 'Update previous document status', type: PropertyType.Checkbox, default: false, + editable: true }, { name: 'prevDocStatus', @@ -47,6 +48,7 @@ export const RevokedStatus = 'Revoked'; title: 'Status value', type: PropertyType.Input, default: '', + editable: true }, ], }, @@ -133,6 +135,8 @@ export class RevocationBlock { async runAction(event: IPolicyEvent): Promise { const userId = event?.user?.userId; const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); + const data = event.data.data; const doc = Array.isArray(data) ? data[0] : data; @@ -197,11 +201,11 @@ export class RevocationBlock { } } - if (ref.options.updatePrevDoc && doc.relationships) { + if (options.updatePrevDoc && doc.relationships) { const prevDocs = await this.findDocumentByMessageIds(doc.relationships); const prevDocument = prevDocs[prevDocs.length - 1]; if (prevDocument) { - prevDocument.option.status = ref.options.prevDocStatus; + prevDocument.option.status = options.prevDocStatus; await PolicyUtils.updateVC(ref, prevDocument, userId); await PolicyUtils.saveDocumentState(ref, prevDocument); } diff --git a/policy-service/src/policy-engine/blocks/revoke-block.ts b/policy-service/src/policy-engine/blocks/revoke-block.ts index 591f83c1fd..4ff8c1bb7a 100644 --- a/policy-service/src/policy-engine/blocks/revoke-block.ts +++ b/policy-service/src/policy-engine/blocks/revoke-block.ts @@ -118,7 +118,8 @@ export class RevokeBlock { async runAction(event: IPolicyEvent): Promise { const userId = event?.user?.userId; const ref = PolicyComponentsUtils.GetBlockRef(this); - const uiMetaData = ref.options.uiMetaData; + const options = await ref.getOptions(event.user); + const uiMetaData = options.uiMetaData; const data = event.data.data; const doc = Array.isArray(data) ? data[0] : data; diff --git a/policy-service/src/policy-engine/blocks/selective-attributes-addon.ts b/policy-service/src/policy-engine/blocks/selective-attributes-addon.ts index c3f18f45c9..216da80604 100644 --- a/policy-service/src/policy-engine/blocks/selective-attributes-addon.ts +++ b/policy-service/src/policy-engine/blocks/selective-attributes-addon.ts @@ -23,6 +23,7 @@ import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-abo label: 'Attributes To Select', title: 'Attributes To Select', type: PropertyType.Array, + editable: true, items: { label: 'Attribute Path', value: '@attributePath', @@ -30,7 +31,8 @@ import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-abo name: 'attributePath', label: 'Attribute Path', title: 'Attribute Path', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }] } }] diff --git a/policy-service/src/policy-engine/blocks/send-to-guardian-block.ts b/policy-service/src/policy-engine/blocks/send-to-guardian-block.ts index 0e673b20b6..24243cfc9d 100644 --- a/policy-service/src/policy-engine/blocks/send-to-guardian-block.ts +++ b/policy-service/src/policy-engine/blocks/send-to-guardian-block.ts @@ -12,6 +12,7 @@ import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfac import { DocumentType } from '../interfaces/document.type.js'; import { FilterQuery } from '@mikro-orm/core'; import { PolicyActionsUtils } from '../policy-actions/utils.js'; +import { PolicyUser } from '@policy-engine/policy-user.js'; /** * Document Operations @@ -172,8 +173,11 @@ export class SendToGuardianBlock { document: IPolicyDocument, operation: Operation, ref: AnyBlockType, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { + const options = await ref.getOptions(user); + let old = await this.getVCRecord(document, operation, ref); if (old) { if (!document.draft) { @@ -190,7 +194,7 @@ export class SendToGuardianBlock { delete document._id; old = await PolicyUtils.saveVC(ref, document, userId); } - if (!ref.options.skipSaveState) { + if (!options.skipSaveState) { await PolicyUtils.saveDocumentState(ref, old); } return old; @@ -363,14 +367,17 @@ export class SendToGuardianBlock { private async sendByType( document: IPolicyDocument, ref: AnyBlockType, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { + const options = await ref.getOptions(user); + document.documentFields = Array.from( PolicyComponentsUtils.getDocumentCacheFields(ref.policyId) ); - switch (ref.options.dataType) { + switch (options.dataType) { case 'vc-documents': { - return await this.updateVCRecord(document, Operation.auto, ref, userId); + return await this.updateVCRecord(document, Operation.auto, ref, userId, user); } case 'did-documents': { return await this.updateDIDRecord(document, Operation.auto, ref); @@ -379,7 +386,7 @@ export class SendToGuardianBlock { return await this.updateApprovalRecord(document, Operation.auto, ref); } default: - throw new BlockActionError(`dataType "${ref.options.dataType}" is unknown`, ref.blockType, ref.uuid) + throw new BlockActionError(`dataType "${options.dataType}" is unknown`, ref.blockType, ref.uuid) } } @@ -393,7 +400,8 @@ export class SendToGuardianBlock { document: IPolicyDocument, type: DocumentType, ref: AnyBlockType, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { const operation: Operation = Operation.auto; document.documentFields = Array.from( @@ -404,7 +412,7 @@ export class SendToGuardianBlock { if (type === DocumentType.DID) { return await this.updateDIDRecord(document, operation, ref); } else if (type === DocumentType.VerifiableCredential) { - return await this.updateVCRecord(document, operation, ref, userId); + return await this.updateVCRecord(document, operation, ref, userId, user); } else if (type === DocumentType.VerifiablePresentation) { return await this.updateVPRecord(document, operation, ref); } @@ -446,17 +454,20 @@ export class SendToGuardianBlock { document: IPolicyDocument, message: Message, ref: AnyBlockType, - userId: string | null + userId: string | null, + user?: PolicyUser ): Promise { try { - const memo = MessageMemo.parseMemo(true, ref.options.memo, document); + const options = await ref.getOptions(user); + + const memo = MessageMemo.parseMemo(true, options.memo, document); message.setMemo(memo); - const topicOwner = this.getTopicOwner(ref, document, ref.options.topicOwner); + const topicOwner = this.getTopicOwner(ref, document, options.topicOwner); const relayerAccount = document.owner === topicOwner ? document.relayerAccount : null; const topic = await PolicyActionsUtils.getOrCreateTopic({ ref, - name: ref.options.topic, + name: options.topic, owner: topicOwner, relayerAccount, memoObj: document, @@ -491,8 +502,10 @@ export class SendToGuardianBlock { private async documentSender( document: IPolicyDocument, userId: string | null, + user?: PolicyUser ): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const type = PolicyUtils.getDocumentType(document); const relayerAccount = await PolicyUtils.getDocumentRelayerAccount(ref, document, userId); const owner = await PolicyUtils.getUserByIssuer(ref, document, userId); @@ -545,13 +558,13 @@ export class SendToGuardianBlock { document.tag = ref.tag; document.relayerAccount = relayerAccount; document.option = Object.assign({}, document.option); - if (ref.options.options) { - for (const option of ref.options.options) { + if (options.options) { + for (const option of options.options) { document.option[option.name] = option.value; } } - if (ref.options.entityType) { - document.type = ref.options.entityType; + if (options.entityType) { + document.type = options.entityType; } // @@ -559,31 +572,31 @@ export class SendToGuardianBlock { // const hash = docObject.toCredentialHash(); const messageHash = message.toHash(); - if (ref.options.dataType) { - if (ref.options.dataType === 'hedera') { - document = await this.sendToHedera(document, message, ref, userId); + if (options.dataType) { + if (options.dataType === 'hedera') { + document = await this.sendToHedera(document, message, ref, userId, user); document.messageHash = messageHash; document = await this.updateMessage(document, type, ref, userId); } else { document.hash = hash; - document = await this.sendByType(document, ref, userId); + document = await this.sendByType(document, ref, userId, user); } - } else if (ref.options.dataSource === 'auto' || !ref.options.dataSource) { + } else if (options.dataSource === 'auto' || !options.dataSource) { if (document.messageHash !== messageHash) { - document = await this.sendToHedera(document, message, ref, userId); + document = await this.sendToHedera(document, message, ref, userId, user); document.messageHash = messageHash; } document.hash = hash; - document = await this.sendToDatabase(document, type, ref, userId); - } else if (ref.options.dataSource === 'database') { + document = await this.sendToDatabase(document, type, ref, userId, user); + } else if (options.dataSource === 'database') { document.hash = hash; - document = await this.sendToDatabase(document, type, ref, userId); - } else if (ref.options.dataSource === 'hedera') { - document = await this.sendToHedera(document, message, ref, userId); + document = await this.sendToDatabase(document, type, ref, userId, user); + } else if (options.dataSource === 'hedera') { + document = await this.sendToHedera(document, message, ref, userId, user); document.messageHash = messageHash; document = await this.updateMessage(document, type, ref, userId); } else { - throw new BlockActionError(`dataSource "${ref.options.dataSource}" is unknown`, ref.blockType, ref.uuid); + throw new BlockActionError(`dataSource "${options.dataSource}" is unknown`, ref.blockType, ref.uuid); } return document; } @@ -621,6 +634,7 @@ export class SendToGuardianBlock { @CatchErrors() async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); ref.log(`runAction`); const tags = await PolicyUtils.getBlockTags(ref); @@ -630,13 +644,13 @@ export class SendToGuardianBlock { const newDocs = []; for (const doc of docs) { PolicyUtils.setDocumentTags(doc, tags); - const newDoc = await this.documentSender(doc, event?.user?.userId); + const newDoc = await this.documentSender(doc, event?.user?.userId, event.user); newDocs.push(newDoc); } event.data.data = newDocs; } else { PolicyUtils.setDocumentTags(docs, tags); - event.data.data = await this.documentSender(docs, event?.user?.userId); + event.data.data = await this.documentSender(docs, event?.user?.userId, event.user); } const olds: IPolicyDocument | IPolicyDocument[] = event.data.old; @@ -653,7 +667,7 @@ export class SendToGuardianBlock { await ref.triggerEvents(PolicyOutputEventType.ReleaseEvent, event.user, null, event.actionStatus); await ref.triggerEvents(PolicyOutputEventType.RefreshEvent, event.user, event.data, event.actionStatus); PolicyComponentsUtils.ExternalEventFn(new ExternalEvent(ExternalEventType.Run, ref, event.user, { - type: (ref.options.dataSource || ref.options.dataType), + type: (options.dataSource || options.dataType), documents: ExternalDocuments(event.data?.data), })); diff --git a/policy-service/src/policy-engine/blocks/set-relationships-block.ts b/policy-service/src/policy-engine/blocks/set-relationships-block.ts index 930b759a82..c2ee96d82a 100644 --- a/policy-service/src/policy-engine/blocks/set-relationships-block.ts +++ b/policy-service/src/policy-engine/blocks/set-relationships-block.ts @@ -32,19 +32,22 @@ import { LocationType } from '@guardian/interfaces'; label: 'Include Accounts', title: 'Include Related Documents Accounts', type: PropertyType.Checkbox, - default: false + default: false, + editable: true }, { name: 'includeTokens', label: 'Include Tokens', title: 'Include Related Documents Tokens', type: PropertyType.Checkbox, - default: false + default: false, + editable: true }, { name: 'changeOwner', label: 'Change Owner', title: 'Change Document Owner', type: PropertyType.Checkbox, - default: false + default: false, + editable: true }] }, variables: [] @@ -59,6 +62,7 @@ export class SetRelationshipsBlock { }) async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); const data: IPolicyDocument[] = await ref.getSources(event.user); const owner = data[0] && data[0].owner || null; const group = data[0] && data[0].group || null; @@ -81,20 +85,20 @@ export class SetRelationshipsBlock { doc.relationships = doc.relationships ? doc.relationships.concat(relationships) : relationships; - if (ref.options.includeAccounts) { + if (options.includeAccounts) { doc.accounts = doc.accounts ? Object.assign(doc.accounts, accounts) : accounts; } - if (ref.options.includeTokens) { + if (options.includeTokens) { doc.tokens = doc.tokens ? Object.assign(doc.tokens, tokens) : tokens; } - if (owner && ref.options.changeOwner) { + if (owner && options.changeOwner) { doc.owner = owner; } - if (group && ref.options.changeOwner) { + if (group && options.changeOwner) { doc.group = group; } } @@ -102,24 +106,24 @@ export class SetRelationshipsBlock { documents.relationships = documents.relationships ? documents.relationships.concat(relationships) : relationships; - if (ref.options.includeAccounts) { + if (options.includeAccounts) { documents.accounts = Object.assign( {}, documents.accounts, accounts ); } - if (ref.options.includeTokens) { + if (options.includeTokens) { documents.tokens = Object.assign( {}, documents.tokens, tokens ); } - if (owner && ref.options.changeOwner) { + if (owner && options.changeOwner) { documents.owner = owner; } - if (group && ref.options.changeOwner) { + if (group && options.changeOwner) { documents.group = group; } } diff --git a/policy-service/src/policy-engine/blocks/split-block.ts b/policy-service/src/policy-engine/blocks/split-block.ts index 845ed9de72..61a3678efd 100644 --- a/policy-service/src/policy-engine/blocks/split-block.ts +++ b/policy-service/src/policy-engine/blocks/split-block.ts @@ -45,12 +45,14 @@ import { RecordActionStep } from '../record-action-step.js'; name: 'threshold', label: 'Threshold', title: 'Threshold', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }, { name: 'sourceField', label: 'Source field', title: 'Source field', - type: PropertyType.Path + type: PropertyType.Path, + editable: true }] }, variables: [] @@ -88,9 +90,10 @@ export class SplitBlock { * @param ref * @param doc */ - private async calcDocValue(ref: IPolicyBlock, doc: IPolicyDocument): Promise { + private async calcDocValue(ref: IPolicyBlock, doc: IPolicyDocument, user?: PolicyUser): Promise { try { - const value = PolicyUtils.getObjectValue(doc, ref.options.sourceField); + const options = await ref.getOptions(user); + const value = PolicyUtils.getObjectValue(doc, options.sourceField); return parseFloat(value); } catch (error) { return 0; @@ -120,9 +123,11 @@ export class SplitBlock { threshold: number, userId: string | null, actionStatusId: string, + user?: PolicyUser ): Promise { let clone = PolicyUtils.cloneVC(ref, document); - PolicyUtils.setObjectValue(clone, ref.options.sourceField, newValue); + const options = await ref.getOptions(user); + PolicyUtils.setObjectValue(clone, options.sourceField, newValue); let vc = VcDocument.fromJsonTree(clone.document); if (document.messageId) { const evidenceSchema = await this.getSchema(); @@ -132,7 +137,7 @@ export class SplitBlock { vc.addEvidence({ type: ['SourceDocument'], messageId: document.messageId, - sourceField: ref.options.sourceField, + sourceField: options.sourceField, sourceValue, threshold, chunkNumber, @@ -174,8 +179,9 @@ export class SplitBlock { userId: string | null, actionStatusId: string ) { - const threshold = parseFloat(ref.options.threshold); - const value = await this.calcDocValue(ref, document); + const options = await ref.getOptions(user); + const threshold = parseFloat(options.threshold); + const value = await this.calcDocValue(ref, document, user); let sum = 0; for (const item of residue) { @@ -192,7 +198,7 @@ export class SplitBlock { if (value < needed) { const maxChunks = 1; const newDoc = await this.createNewDoc( - ref, root, document, value, maxChunks, maxChunks, value, threshold, userId, actionStatusId + ref, root, document, value, maxChunks, maxChunks, value, threshold, userId, actionStatusId, user ); residue.push(ref.databaseServer.createResidue( ref.policyId, @@ -207,7 +213,7 @@ export class SplitBlock { const maxChunks = (count > 0 ? count : 0) + (end > 0 ? 1 : 0) + 1; const newDoc1 = await this.createNewDoc( - ref, root, document, needed, 1, maxChunks, value, threshold, userId, actionStatusId + ref, root, document, needed, 1, maxChunks, value, threshold, userId, actionStatusId, user ); residue.push(ref.databaseServer.createResidue( ref.policyId, @@ -222,7 +228,7 @@ export class SplitBlock { if (count > 0) { for (let i = 0; i < count; i++) { const newDocN = await this.createNewDoc( - ref, root, document, threshold, i + 2, maxChunks, value, threshold, userId, actionStatusId + ref, root, document, threshold, i + 2, maxChunks, value, threshold, userId, actionStatusId, user ); result.push([ref.databaseServer.createResidue( ref.policyId, @@ -235,7 +241,7 @@ export class SplitBlock { } if (end > 0) { const newDocL = await this.createNewDoc( - ref, root, document, end, maxChunks, maxChunks, value, threshold, userId, actionStatusId + ref, root, document, end, maxChunks, maxChunks, value, threshold, userId, actionStatusId, user ); residue.push(ref.databaseServer.createResidue( ref.policyId, diff --git a/policy-service/src/policy-engine/blocks/step-block.ts b/policy-service/src/policy-engine/blocks/step-block.ts index 301f688243..39b9edf0b9 100644 --- a/policy-service/src/policy-engine/blocks/step-block.ts +++ b/policy-service/src/policy-engine/blocks/step-block.ts @@ -35,23 +35,27 @@ import { RecordActionStep } from '../record-action-step.js'; name: 'cyclic', label: 'Cyclic', title: 'Restart the block when the final step is reached?', - type: PropertyType.Checkbox + type: PropertyType.Checkbox, + editable: true }, { name: 'finalBlocks', label: 'Final steps', title: 'Final steps', type: PropertyType.MultipleSelect, - items: SelectItemType.Children + items: SelectItemType.Children, + editable: true }, { name: 'uiMetaData', label: 'UI', title: 'UI Properties', type: PropertyType.Group, + editable: true, properties: [{ name: 'title', label: 'Title', title: 'Title', - type: PropertyType.Input + type: PropertyType.Input, + editable: true }] }] }, @@ -75,6 +79,7 @@ export class InterfaceStepBlock { this.state = {} const ref = PolicyComponentsUtils.GetBlockRef(this); + this.endIndexes[ref.children.length - 1] = true; if (ref.options?.finalBlocks && Array.isArray(ref.options.finalBlocks)) { for (const finalBlock of ref.options.finalBlocks) { @@ -95,6 +100,7 @@ export class InterfaceStepBlock { }) async changeStep(user: PolicyUser, data: any, target: IPolicyBlock, actionStatus: RecordActionStep) { const ref = PolicyComponentsUtils.GetBlockRef(this); + let blockState: any; if (!this.state.hasOwnProperty(user.id)) { blockState = {}; @@ -133,8 +139,9 @@ export class InterfaceStepBlock { }) async releaseChild(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); const index = ref.children.findIndex(c => c.uuid === event.sourceId); - if ((ref.options.cyclic && index !== -1) && (this.endIndexes[index])) { + if ((options.cyclic && index !== -1) && (this.endIndexes[index])) { const user = event.user; if (user) { let blockState: any; @@ -159,6 +166,7 @@ export class InterfaceStepBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); let blockState: any; if (!this.state.hasOwnProperty(user.id)) { blockState = {}; @@ -177,7 +185,7 @@ export class InterfaceStepBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - uiMetaData: ref.options?.uiMetaData, + uiMetaData: options?.uiMetaData, index: blockState.index }; } diff --git a/policy-service/src/policy-engine/blocks/switch-block.ts b/policy-service/src/policy-engine/blocks/switch-block.ts index fe1daa4719..dfbc7f750d 100644 --- a/policy-service/src/policy-engine/blocks/switch-block.ts +++ b/policy-service/src/policy-engine/blocks/switch-block.ts @@ -3,7 +3,7 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; import { VcDocumentDefinition as VcDocument } from '@guardian/common'; import { PolicyUtils } from '../helpers/utils.js'; import { IPolicyEvent, PolicyInputEventType, PolicyOutputEventType } from '../interfaces/index.js'; -import { ChildrenType, ControlType } from '../interfaces/block-about.js'; +import { ChildrenType, ControlType, PropertyType } from '../interfaces/block-about.js'; import { PolicyUser } from '../policy-user.js'; import { IPolicyDocument, IPolicyEventState } from '../policy-engine.interface.js'; import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfaces/external-event.js'; @@ -29,7 +29,70 @@ import { LocationType } from '@guardian/interfaces'; output: [ PolicyOutputEventType.RefreshEvent ], - defaultEvent: false + defaultEvent: false, + properties: [ + { + name: 'executionFlow', + label: 'Execution Flow', + title: 'Execution Flow', + type: PropertyType.Select, + editable: true, + items: [ + {label: 'First True', value: 'firstTrue'}, + {label: 'All True', value: 'allTrue'} + ] + }, { + name: 'conditions', + label: 'Conditions', + title: 'Conditions', + type: PropertyType.Array, + editable: true, + items: { + label: 'Condition Type', + value: '', + properties: [ + { + name: 'tag', + label: 'Condition tag', + title: 'Condition tag', + type: PropertyType.Input, + editable: true + }, + { + name: 'type', + label: 'Type', + title: 'Type', + type: PropertyType.Select, + items: [ + { label: 'Equal', value: 'equal'}, + { label: 'Not Equal', value: 'not_equal'}, + { label: 'Unconditional', value: 'unconditional'} + ], + editable: true + }, + { + name: 'condition', + label: 'Condition', + title: 'Condition', + type: PropertyType.Input, + editable: true + }, + { + name: 'actor', + label: 'Actor', + title: 'Actor', + type: PropertyType.Select, + editable: true, + items: [ + { label: 'Current User', value: ''}, + { label: 'Document Owner', value: 'owner'}, + { label: 'Document Issuer', value: 'issuer'} + ] + } + ] + } + }, + ] }, variables: [] }) @@ -97,7 +160,7 @@ export class SwitchBlock { }) async runAction(event: IPolicyEvent) { const ref = PolicyComponentsUtils.GetBlockRef(this); - + const options = await ref.getOptions(event.user); ref.log(`switch: ${event.user?.id}`); const docs: IPolicyDocument | IPolicyDocument[] = event.data.data; @@ -117,7 +180,7 @@ export class SwitchBlock { const scope = this.getScope(docs); - const { conditions, executionFlow } = ref.options; + const { conditions, executionFlow } = options; const tags: string[] = []; for (const condition of conditions) { const type = condition.type as string; diff --git a/policy-service/src/policy-engine/blocks/token-action-block.ts b/policy-service/src/policy-engine/blocks/token-action-block.ts index 5530c1fc20..ccb220db2e 100644 --- a/policy-service/src/policy-engine/blocks/token-action-block.ts +++ b/policy-service/src/policy-engine/blocks/token-action-block.ts @@ -90,6 +90,7 @@ export class TokenActionBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); return { id: ref.uuid, blockType: ref.blockType, @@ -98,7 +99,7 @@ export class TokenActionBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - uiMetaData: ref.options?.uiMetaData + uiMetaData: options?.uiMetaData }; } @@ -118,17 +119,18 @@ export class TokenActionBlock { async runAction(event: IPolicyEvent) { const userId = event?.user?.userId; const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(event.user); ref.log(`runAction`); - const field = ref.options.accountId; + const field = options.accountId; const documents = event?.data?.data; const doc = Array.isArray(documents) ? documents[0] : documents; let token: Token | null; - if (!ref.options.useTemplate) { - token = await ref.databaseServer.getToken(ref.options.tokenId); + if (!options.useTemplate) { + token = await ref.databaseServer.getToken(options.tokenId); } - if (ref.options.useTemplate && doc && doc.tokens) { - token = await ref.databaseServer.getToken(doc.tokens[ref.options.template], ref.dryRun); + if (options.useTemplate && doc && doc.tokens) { + token = await ref.databaseServer.getToken(doc.tokens[options.template], ref.dryRun); } if (!token) { throw new BlockActionError('Bad token id', ref.blockType, ref.uuid); @@ -146,7 +148,7 @@ export class TokenActionBlock { throw new BlockActionError('Hedera Account not found.', ref.blockType, ref.uuid); } - switch (ref.options.action) { + switch (options.action) { case 'associate': { await PolicyActionsUtils.associateToken({ ref, @@ -207,7 +209,7 @@ export class TokenActionBlock { await ref.triggerEvents(PolicyOutputEventType.ReleaseEvent, event.user, null, event.actionStatus); await ref.triggerEvents(PolicyOutputEventType.RefreshEvent, event.user, event.data, event.actionStatus); PolicyComponentsUtils.ExternalEventFn(new ExternalEvent(ExternalEventType.Run, ref, event.user, { - action: ref.options.action + action: options.action })); ref.backup(); diff --git a/policy-service/src/policy-engine/blocks/token-confirmation-block.ts b/policy-service/src/policy-engine/blocks/token-confirmation-block.ts index 644ebaa3e1..d911cb9cc1 100644 --- a/policy-service/src/policy-engine/blocks/token-confirmation-block.ts +++ b/policy-service/src/policy-engine/blocks/token-confirmation-block.ts @@ -79,10 +79,11 @@ export class TokenConfirmationBlock { /** * Get Schema */ - async getToken(): Promise { + async getToken(user?: PolicyUser): Promise { if (!this.token) { const ref = PolicyComponentsUtils.GetBlockRef(this); - this.token = await ref.databaseServer.getToken(ref.options.tokenId); + const options = await ref.getOptions(user); + this.token = await ref.databaseServer.getToken(options.tokenId); } return this.token; } @@ -93,8 +94,9 @@ export class TokenConfirmationBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const blockState: any = this.state[user?.id] || {}; - const token = await this.getToken(); + const token = await this.getToken(user); const block: IPolicyGetData = { id: ref.uuid, blockType: 'tokenConfirmationBlock', @@ -103,7 +105,7 @@ export class TokenConfirmationBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - action: ref.options.action, + action: options.action, accountId: blockState.accountId, tokenName: token.tokenName, tokenSymbol: token.tokenSymbol, @@ -133,7 +135,7 @@ export class TokenConfirmationBlock { } if (data.action === 'confirm') { - await this.confirm(ref, data, blockState, user.userId); + await this.confirm(ref, data, blockState, user.userId, user); } return { @@ -145,6 +147,7 @@ export class TokenConfirmationBlock { action: 'confirm' | 'skip' }, actionStatus: RecordActionStep) { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); ref.log(`setData`); if (!data) { @@ -160,7 +163,7 @@ export class TokenConfirmationBlock { await ref.triggerEvents(PolicyOutputEventType.RefreshEvent, blockState.user, blockState.data, actionStatus); PolicyComponentsUtils.ExternalEventFn(new ExternalEvent(ExternalEventType.Set, ref, blockState.user, { userAction: data.action, - action: ref.options.action + action: options.action })); } @@ -205,31 +208,34 @@ export class TokenConfirmationBlock { hederaAccountKey: string }, state: any, - userId: string | null) { + userId: string | null, + user?: PolicyUser) { const account = { id: userId, hederaAccountId: state.accountId, hederaAccountKey: data.hederaAccountKey } + const options = await ref.getOptions(user); + if (!account.hederaAccountKey) { throw new BlockActionError(`Key value is unknown`, ref.blockType, ref.uuid) } let token: any; - if (ref.options.useTemplate) { + if (options.useTemplate) { if (state.tokenId) { token = await ref.databaseServer.getToken(state.tokenId, ref.dryRun); } } else { - token = await this.getToken(); + token = await this.getToken(user); } if (!token) { throw new BlockActionError('Bad token id', ref.blockType, ref.uuid); } - switch (ref.options.action) { + switch (options.action) { case 'associate': { await PolicyUtils.associate(ref, token, account, userId); break; @@ -260,15 +266,17 @@ export class TokenConfirmationBlock { const ref = PolicyComponentsUtils.GetBlockRef(this); ref.log(`runAction`); - const field = ref.options.accountId; + const options = await ref.getOptions(event.user); + + const field = options.accountId; if (event) { const documents = event.data?.data; const id = event.user?.id; const userId = event?.user?.userId; const doc = Array.isArray(documents) ? documents[0] : documents; let tokenId: string; - if (ref.options.useTemplate && doc && doc.tokens) { - tokenId = doc.tokens[ref.options.template]; + if (options.useTemplate && doc && doc.tokens) { + tokenId = doc.tokens[options.template]; } let relayerAccount: string = null; diff --git a/policy-service/src/policy-engine/blocks/transformation-button-block.ts b/policy-service/src/policy-engine/blocks/transformation-button-block.ts index a9a93923c9..2aff9ce223 100644 --- a/policy-service/src/policy-engine/blocks/transformation-button-block.ts +++ b/policy-service/src/policy-engine/blocks/transformation-button-block.ts @@ -30,25 +30,28 @@ import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfac defaultEvent: false, properties: [ { - 'name': 'buttonName', - 'label': 'Button name', - 'title': 'Button name', - 'type': PropertyType.Input, - 'default': '' + name: 'buttonName', + label: 'Button name', + title: 'Button name', + type: PropertyType.Input, + default: '', + editable: true }, { - 'name': 'url', - 'label': 'Url', - 'title': 'Url', - 'type': PropertyType.Input, - 'default': '' + name: 'url', + label: 'Url', + title: 'Url', + type: PropertyType.Input, + default: '', + editable: true }, { - 'name': 'hideWhenDiscontinued', - 'label': 'Hide when discontinued', - 'title': 'Hide when discontinued', - 'type': PropertyType.Checkbox, - 'default': false + name: 'hideWhenDiscontinued', + label: 'Hide when discontinued', + title: 'Hide when discontinued', + type: PropertyType.Checkbox, + default: false, + editable: true }, ] }, @@ -63,6 +66,7 @@ export class TransformationButtonBlock { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const data: IPolicyGetData = { id: ref.uuid, blockType: ref.blockType, @@ -71,11 +75,11 @@ export class TransformationButtonBlock { ref.actionType === LocationType.REMOTE && user.location === LocationType.REMOTE ), - type: ref.options.type, - uiMetaData: ref.options.uiMetaData, - user: ref.options.user, - buttonName: ref.options.buttonName, - hideWhenDiscontinued: !!ref.options.hideWhenDiscontinued, + type: options.type, + uiMetaData: options.uiMetaData, + user: options.user, + buttonName: options.buttonName, + hideWhenDiscontinued: !!options.hideWhenDiscontinued, } return data; } @@ -96,6 +100,7 @@ export class TransformationButtonBlock { tag: any }, _, actionStatus): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); const data: IPolicyDocument = blockData.document; const state: IPolicyEventState = { data }; const eventData = await ref.triggerEventSync(PolicyInputEventType.GetDataEvent, user, state, actionStatus); @@ -107,7 +112,7 @@ export class TransformationButtonBlock { return { data: eventData, - url: ref.options?.url ?? '' + url: options && options.url ? options.url : '' }; } } diff --git a/policy-service/src/policy-engine/blocks/transformation-ui-addon.ts b/policy-service/src/policy-engine/blocks/transformation-ui-addon.ts index 3971f85db1..bd4342b2f6 100644 --- a/policy-service/src/policy-engine/blocks/transformation-ui-addon.ts +++ b/policy-service/src/policy-engine/blocks/transformation-ui-addon.ts @@ -27,7 +27,8 @@ import { PolicyComponentsUtils } from '../policy-components-utils.js'; name: 'expression', label: 'Expression', title: 'Expression', - type: PropertyType.Code + type: PropertyType.Code, + editable: true } ] }, @@ -41,7 +42,8 @@ export class TransformationUIAddon { */ async getData(user: PolicyUser): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); - const options = PolicyComponentsUtils.GetBlockUniqueOptionsObject(this); + const options = await ref.getOptions(user); + return { id: ref.uuid, blockType: ref.blockType, diff --git a/policy-service/src/policy-engine/blocks/upload-vc-document-block.ts b/policy-service/src/policy-engine/blocks/upload-vc-document-block.ts index e727e7dacd..4ae5ff1143 100644 --- a/policy-service/src/policy-engine/blocks/upload-vc-document-block.ts +++ b/policy-service/src/policy-engine/blocks/upload-vc-document-block.ts @@ -161,6 +161,7 @@ export class UploadVcDocumentBlock { }) async setData(user: PolicyUser, data: any, _, actionStatus: RecordActionStep): Promise { const ref = PolicyComponentsUtils.GetBlockRef(this); + const options = await ref.getOptions(user); if (!user.did) { throw new BlockActionError('User have no any did', ref.blockType, ref.uuid); @@ -193,8 +194,8 @@ export class UploadVcDocumentBlock { const tags = await PolicyUtils.getBlockTags(ref); PolicyUtils.setDocumentTags(doc, tags); - doc.type = ref.options.entityType; - doc.schema = ref.options.schema; + doc.type = options.entityType; + doc.schema = options.schema; doc.signature = DocumentSignature.VERIFIED; retArray.push(doc); diff --git a/policy-service/src/policy-engine/helpers/decorators/basic-block.ts b/policy-service/src/policy-engine/helpers/decorators/basic-block.ts index ed68315240..b3c4da1a04 100644 --- a/policy-service/src/policy-engine/helpers/decorators/basic-block.ts +++ b/policy-service/src/policy-engine/helpers/decorators/basic-block.ts @@ -6,12 +6,13 @@ import { AnyBlockType, IPolicyBlock, IPolicyDocument, ISerializedBlock, } from ' import { PolicyComponentsUtils } from '../../policy-components-utils.js'; import { IPolicyEvent, PolicyLink } from '../../interfaces/policy-event.js'; import { PolicyInputEventType, PolicyOutputEventType } from '../../interfaces/policy-event-type.js'; -import { DatabaseServer, Policy } from '@guardian/common'; +import { DatabaseServer, Policy, PolicyParameters } from '@guardian/common'; import deepEqual from 'deep-equal'; import { PolicyUser } from '../../policy-user.js'; import { ComponentsService } from '../components-service.js'; import { IDebugContext } from '../../block-engine/block-result.js'; import { RecordActionStep } from '../../record-action-step.js'; +import { PolicyUtils } from '../utils.js'; /** * Basic block decorator @@ -930,6 +931,79 @@ export function BasicBlock(options: Partial) { this.policyId, this.uuid, did, name, value, true ); } + + setPropValue(properties:any, path:string, value:any) { + if (!path) { + return; + } + + const keys = path.split('.'); + const last = keys.pop(); + if (!last) { + return; + } + + let current = properties; + + for (const key of keys) { + if(!(current[key] && typeof current[key] === 'object')) { + current[key] = {}; + } + + current = current[key]; + } + + current[last] = value; + } + + public async getOptions(user?: any) { + if(!user) { + return this.options; + } + const row = await DatabaseServer.getPolicyParameters(user.did, this.policyId); + let properties; + if(!row || row.updated || !row.properties || Object.keys(row.properties).length === 0) { + properties = {}; + const policyRows = await this.databaseServer.find(PolicyParameters, { policyId: this.policyId }); + for (const item of policyRows) { + for (const config of item.config ?? []) { + if( + config.applyTo.includes('All') || + config.applyTo.includes(user.role) || + (config.applyTo.includes('Self') && item.userDID === user.did) + ) { + if(!properties[config.blockTag]) { + properties[config.blockTag] = {}; + } + this.setPropValue(properties[config.blockTag], config.propertyPath, config.value); + } + } + } + if(row) { + row.properties = properties; + this.databaseServer.update(PolicyParameters, { + policyId: row.policyId, + userDID: row.userDID, + }, row); + } else { + this.databaseServer.save(PolicyParameters, { + policyId: this.policyId, + userDID: user.did, + config: [], + updated: false, + properties + }); + } + } else { + properties = row.properties; + } + + if(properties && properties[this.tag]) { + return PolicyUtils.deepAssign({}, this.options, properties[this.tag]); + } else { + return this.options; + } + } }; }; } diff --git a/policy-service/src/policy-engine/helpers/utils.ts b/policy-service/src/policy-engine/helpers/utils.ts index 13449754fa..c41b9c07fe 100644 --- a/policy-service/src/policy-engine/helpers/utils.ts +++ b/policy-service/src/policy-engine/helpers/utils.ts @@ -2012,4 +2012,38 @@ export class PolicyUtils { } } } + + public static deepAssign(target, ...sources) { + if (target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + const isObject = (obj) => + obj && typeof obj === 'object' && !Array.isArray(obj); + + for (const source of sources) { + if (!isObject(source) && !Array.isArray(source)) { + continue; + } + + for (const key of Object.keys(source)) { + const value = source[key]; + + if (Array.isArray(value)) { + target[key] = value.map((item) => + isObject(item) ? PolicyUtils.deepAssign({}, item) : item + ); + } else if (isObject(value)) { + if (!isObject(target[key])) { + target[key] = {}; + } + PolicyUtils.deepAssign(target[key], value); + } else { + target[key] = value; + } + } + } + + return target; + } } diff --git a/policy-service/src/policy-engine/interfaces/block-about.ts b/policy-service/src/policy-engine/interfaces/block-about.ts index 6754ff194d..805af8cb4a 100644 --- a/policy-service/src/policy-engine/interfaces/block-about.ts +++ b/policy-service/src/policy-engine/interfaces/block-about.ts @@ -77,6 +77,14 @@ export interface BlockProperties { * Visible expression */ visible?: string; + + editable?: boolean; + + confirmation?: { + title: string, + description: string, + condition: any + } } /** @@ -107,6 +115,8 @@ export interface CheckboxProperties extends BlockProperties { * Property type */ type: PropertyType.Checkbox; + + text?: string; } /** diff --git a/policy-service/src/policy-engine/policy-engine.interface.ts b/policy-service/src/policy-engine/policy-engine.interface.ts index f4c4691f92..342723282a 100644 --- a/policy-service/src/policy-engine/policy-engine.interface.ts +++ b/policy-service/src/policy-engine/policy-engine.interface.ts @@ -475,6 +475,8 @@ export interface IPolicyBlock { value: T, user?: PolicyUser | string ): Promise; + + getOptions(user?: PolicyUser | null): Promise; } /** diff --git a/swagger.yaml b/swagger.yaml index 7c199cc0d3..eceac6730e 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -9119,6 +9119,80 @@ paths: summary: Get all version VC documents. tags: - policies + /policies/{policyId}/parameters: + post: + description: Save policy config with values to the PolicyParameters tale + operationId: PolicyApi_savePolicyParametersValues + parameters: + - name: policyId + required: true + in: path + schema: + type: string + requestBody: + required: true + description: Policy parameters. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PolicyEditableFieldDTO' + responses: + '200': + description: Successful operation. + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + security: + - bearer: [] + summary: Save policy config with values + tags: + - policies + /policies/{policyId}/parameters/config: + get: + description: Get policy parameters. + operationId: PolicyApi_getPolicyParametersConfig + parameters: + - name: policyId + required: true + in: path + schema: + type: string + requestBody: + required: true + description: Policy parameters. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PolicyEditableFieldDTO' + responses: + '200': + description: Successful operation. + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + security: + - bearer: [] + summary: Get policy parameters. + tags: + - policies /schema/{schemaId}: get: description: >- @@ -18976,6 +19050,9 @@ components: - progress - resultId - result + PolicyEditableFieldDTO: + type: object + properties: {} IgnoreRuleDTO: type: object properties: @@ -19054,6 +19131,10 @@ components: example: 1.0.0 originalChanged: type: string + editableParametersSettings: + type: array + items: + $ref: '#/components/schemas/PolicyEditableFieldDTO' config: type: object additionalProperties: true @@ -19175,6 +19256,7 @@ components: - codeVersion - createDate - version + - editableParametersSettings - config - userRole - userRoles @@ -21284,6 +21366,28 @@ components: - id - name - type + PolicyParametersDTO: + type: object + properties: + userId: + type: string + example: '000000000000000000000001' + policyId: + type: string + example: 00000000-0000-0000-0000-000000000000 + config: + type: array + items: + $ref: '#/components/schemas/PolicyEditableFieldDTO' + updated: + type: boolean + example: true + propValue: + type: string + example: Prop Value + required: + - userId + - policyId VersionSchemaDTO: type: object properties: