From 689ceee173464cef8a2ccf0635538218f792d125 Mon Sep 17 00:00:00 2001 From: ndigirigijohn Date: Mon, 24 Feb 2025 11:42:34 +0300 Subject: [PATCH 1/2] validation actions --- .../Domain/ProcessFlow/Actions/ActionInput.cs | 9 +- .../Domain/ProcessFlow/Actions/EActionType.cs | 4 +- .../Validation/CustomValidationAction.cs | 18 + .../Actions/Validation/W3cValidationAction.cs | 18 + .../20250224083212_newActions.Designer.cs | 557 ++++++++++++++++++ .../Migrations/20250224083212_newActions.cs | 22 + ...250224083958_newActionsUpdates.Designer.cs | 557 ++++++++++++++++++ .../20250224083958_newActionsUpdates.cs | 22 + .../CustomValidationComponent.razor | 183 ++++++ .../Validation/W3cValidationComponent.razor | 204 +++++++ .../PropertyWindow/PropertyEditor.razor | 89 +-- .../PropertyWindow/PropertyWindow.razor | 175 +++--- .../wwwroot/app.css | 25 - 13 files changed, 1722 insertions(+), 161 deletions(-) create mode 100644 Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/CustomValidationAction.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/W3cValidationAction.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.Designer.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.Designer.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.cs create mode 100644 Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/CustomValidationComponent.razor create mode 100644 Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/W3cValidationComponent.razor diff --git a/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/ActionInput.cs b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/ActionInput.cs index d783de9..25bb365 100644 --- a/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/ActionInput.cs +++ b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/ActionInput.cs @@ -2,6 +2,7 @@ using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Issuance; using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Outgoing; using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Verification; +using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; namespace Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions; @@ -12,7 +13,7 @@ namespace Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions; [JsonDerivedType(typeof(IssueW3CSdCredential), typeDiscriminator: "w3cSdCredential")] [JsonDerivedType(typeof(IssueAnoncredCredential), typeDiscriminator: "anoncredCredential")] -// VerifyCredentials +// Verification [JsonDerivedType(typeof(VerifyW3cCredential), typeDiscriminator: "verifyW3cCredential")] [JsonDerivedType(typeof(VerifyW3cSdCredential), typeDiscriminator: "verifyW3cSdCredential")] [JsonDerivedType(typeof(VerifyAnoncredCredential), typeDiscriminator: "verifyAnoncredCredential")] @@ -22,8 +23,12 @@ namespace Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions; [JsonDerivedType(typeof(HttpAction), typeDiscriminator: "http")] [JsonDerivedType(typeof(EmailAction), typeDiscriminator: "email")] +// Validation +[JsonDerivedType(typeof(W3cValidationAction), typeDiscriminator: "w3cValidation")] +[JsonDerivedType(typeof(CustomValidationAction), typeDiscriminator: "customValidation")] + public abstract class ActionInput { [JsonPropertyName("id")] public Guid Id { get; set; } -} +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/EActionType.cs b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/EActionType.cs index 86e686c..c005094 100644 --- a/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/EActionType.cs +++ b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/EActionType.cs @@ -10,5 +10,7 @@ public enum EActionType VerifyAnoncredCredential, DIDComm, Http, - Email + Email, + W3cValidation, + CustomValidation } \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/CustomValidationAction.cs b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/CustomValidationAction.cs new file mode 100644 index 0000000..9a4a277 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/CustomValidationAction.cs @@ -0,0 +1,18 @@ +using Blocktrust.CredentialWorkflow.Core.Domain.Common; + +namespace Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; + +public class CustomValidationAction : ActionInput +{ + public ParameterReference DataReference { get; set; } = new(); + public List ValidationRules { get; set; } = new(); + public string FailureAction { get; set; } = "Stop"; + public Guid? SkipToActionId { get; set; } +} + +public class CustomValidationRule +{ + public string Name { get; set; } = string.Empty; + public string Expression { get; set; } = string.Empty; + public string ErrorMessage { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/W3cValidationAction.cs b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/W3cValidationAction.cs new file mode 100644 index 0000000..48570b5 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Domain/ProcessFlow/Actions/Validation/W3cValidationAction.cs @@ -0,0 +1,18 @@ +using Blocktrust.CredentialWorkflow.Core.Domain.Common; + +namespace Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; + +public class W3cValidationAction : ActionInput +{ + public ParameterReference CredentialReference { get; set; } = new(); + public List ValidationRules { get; set; } = new(); + public string FailureAction { get; set; } = "Stop"; + public Guid? SkipToActionId { get; set; } + public string ErrorMessageTemplate { get; set; } = string.Empty; +} + +public class ValidationRule +{ + public string Type { get; set; } = string.Empty; + public string Configuration { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.Designer.cs b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.Designer.cs new file mode 100644 index 0000000..b9dcfc3 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.Designer.cs @@ -0,0 +1,557 @@ +// +using System; +using Blocktrust.CredentialWorkflow.Core; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Blocktrust.CredentialWorkflow.Core.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250224083212_newActions")] + partial class newActions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.DIDComm.PeerDIDEntity", b => + { + b.Property("PeerDIDEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .IsUnicode(true) + .HasColumnType("character varying(200)"); + + b.Property("PeerDID") + .IsRequired() + .HasMaxLength(5000) + .IsUnicode(true) + .HasColumnType("character varying(5000)"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.HasKey("PeerDIDEntityId"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("PeerDIDEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.DIDComm.PeerDIDSecretEntity", b => + { + b.Property("PeerDIDSecretId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Kid") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .IsUnicode(true) + .HasColumnType("character varying(2000)"); + + b.Property("VerificationMaterialFormat") + .HasColumnType("integer"); + + b.Property("VerificationMethodType") + .HasColumnType("integer"); + + b.HasKey("PeerDIDSecretId"); + + b.ToTable("PeerDIDSecrets"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("SomeOtherData") + .HasColumnType("text"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Outcome.WorkflowOutcomeEntity", b => + { + b.Property("WorkflowOutcomeEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActionOutcomesJson") + .HasColumnType("text"); + + b.Property("EndedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("ExecutionContext") + .HasColumnType("text"); + + b.Property("StartedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkflowEntityId") + .HasColumnType("uuid"); + + b.Property("WorkflowOutcomeState") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("WorkflowOutcomeEntityId"); + + b.HasIndex("WorkflowEntityId"); + + b.ToTable("WorkflowOutcomeEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.IssuingKeyEntity", b => + { + b.Property("IssuingKeyId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Did") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(true) + .HasColumnType("character varying(100)"); + + b.Property("PrivateKey") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.HasKey("IssuingKeyId"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("IssuingKeys"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", b => + { + b.Property("TenantEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(true) + .HasColumnType("character varying(100)"); + + b.HasKey("TenantEntityId"); + + b.ToTable("TenantEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", b => + { + b.Property("WorkflowEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRunable") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("character varying(100)"); + + b.Property("ProcessFlowJson") + .IsUnicode(false) + .HasColumnType("text"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.Property("UpdatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkflowState") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("WorkflowEntityId"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("WorkflowEntities"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.DIDComm.PeerDIDEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", null) + .WithMany("PeerDIDEntities") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", "TenantEntity") + .WithMany("ApplicationUsers") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("TenantEntity"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Outcome.WorkflowOutcomeEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", "WorkflowEntity") + .WithMany("WorkflowOutcomeEntities") + .HasForeignKey("WorkflowEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WorkflowEntity"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.IssuingKeyEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", null) + .WithMany("IssuingKeys") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", "TenantEntity") + .WithMany("WorkflowEntities") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TenantEntity"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", b => + { + b.Navigation("ApplicationUsers"); + + b.Navigation("IssuingKeys"); + + b.Navigation("PeerDIDEntities"); + + b.Navigation("WorkflowEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", b => + { + b.Navigation("WorkflowOutcomeEntities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.cs b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.cs new file mode 100644 index 0000000..7997ac5 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083212_newActions.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Blocktrust.CredentialWorkflow.Core.Migrations +{ + /// + public partial class newActions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.Designer.cs b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.Designer.cs new file mode 100644 index 0000000..771b97b --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.Designer.cs @@ -0,0 +1,557 @@ +// +using System; +using Blocktrust.CredentialWorkflow.Core; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Blocktrust.CredentialWorkflow.Core.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250224083958_newActionsUpdates")] + partial class newActionsUpdates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.DIDComm.PeerDIDEntity", b => + { + b.Property("PeerDIDEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .IsUnicode(true) + .HasColumnType("character varying(200)"); + + b.Property("PeerDID") + .IsRequired() + .HasMaxLength(5000) + .IsUnicode(true) + .HasColumnType("character varying(5000)"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.HasKey("PeerDIDEntityId"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("PeerDIDEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.DIDComm.PeerDIDSecretEntity", b => + { + b.Property("PeerDIDSecretId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Kid") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .IsUnicode(true) + .HasColumnType("character varying(2000)"); + + b.Property("VerificationMaterialFormat") + .HasColumnType("integer"); + + b.Property("VerificationMethodType") + .HasColumnType("integer"); + + b.HasKey("PeerDIDSecretId"); + + b.ToTable("PeerDIDSecrets"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("SomeOtherData") + .HasColumnType("text"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Outcome.WorkflowOutcomeEntity", b => + { + b.Property("WorkflowOutcomeEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActionOutcomesJson") + .HasColumnType("text"); + + b.Property("EndedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("ExecutionContext") + .HasColumnType("text"); + + b.Property("StartedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkflowEntityId") + .HasColumnType("uuid"); + + b.Property("WorkflowOutcomeState") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("WorkflowOutcomeEntityId"); + + b.HasIndex("WorkflowEntityId"); + + b.ToTable("WorkflowOutcomeEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.IssuingKeyEntity", b => + { + b.Property("IssuingKeyId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Did") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("KeyType") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(true) + .HasColumnType("character varying(100)"); + + b.Property("PrivateKey") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(1000) + .IsUnicode(true) + .HasColumnType("character varying(1000)"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.HasKey("IssuingKeyId"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("IssuingKeys"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", b => + { + b.Property("TenantEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(true) + .HasColumnType("character varying(100)"); + + b.HasKey("TenantEntityId"); + + b.ToTable("TenantEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", b => + { + b.Property("WorkflowEntityId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRunable") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(false) + .HasColumnType("character varying(100)"); + + b.Property("ProcessFlowJson") + .IsUnicode(false) + .HasColumnType("text"); + + b.Property("TenantEntityId") + .HasColumnType("uuid"); + + b.Property("UpdatedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkflowState") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("WorkflowEntityId"); + + b.HasIndex("TenantEntityId"); + + b.ToTable("WorkflowEntities"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.DIDComm.PeerDIDEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", null) + .WithMany("PeerDIDEntities") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", "TenantEntity") + .WithMany("ApplicationUsers") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("TenantEntity"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Outcome.WorkflowOutcomeEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", "WorkflowEntity") + .WithMany("WorkflowOutcomeEntities") + .HasForeignKey("WorkflowEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WorkflowEntity"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.IssuingKeyEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", null) + .WithMany("IssuingKeys") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", "TenantEntity") + .WithMany("WorkflowEntities") + .HasForeignKey("TenantEntityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TenantEntity"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Blocktrust.CredentialWorkflow.Core.Entities.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Tenant.TenantEntity", b => + { + b.Navigation("ApplicationUsers"); + + b.Navigation("IssuingKeys"); + + b.Navigation("PeerDIDEntities"); + + b.Navigation("WorkflowEntities"); + }); + + modelBuilder.Entity("Blocktrust.CredentialWorkflow.Core.Entities.Workflow.WorkflowEntity", b => + { + b.Navigation("WorkflowOutcomeEntities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.cs b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.cs new file mode 100644 index 0000000..392da56 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Migrations/20250224083958_newActionsUpdates.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Blocktrust.CredentialWorkflow.Core.Migrations +{ + /// + public partial class newActionsUpdates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/CustomValidationComponent.razor b/Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/CustomValidationComponent.razor new file mode 100644 index 0000000..0c975b9 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/CustomValidationComponent.razor @@ -0,0 +1,183 @@ +@using Blocktrust.CredentialWorkflow.Core.Domain.Common +@using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation +@using WorkflowAction = Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Action + +@namespace Blocktrust.CredentialWorkflow.Web.Components.Features.Actions.Validation + +
+
+ +
+

Data Source

+
+ + + @if (ActionInput.DataReference.Source == ParameterSource.TriggerInput) + { + + } + else if (ActionInput.DataReference.Source == ParameterSource.ActionOutcome) + { + + } +
+
+ + +
+
+

Validation Rules

+ +
+ + @foreach (var rule in ActionInput.ValidationRules) + { +
+
+ + +
+ +
+ +

+ Use 'data' to reference the input value. Support basic JavaScript expressions. +

+
+ +
+ +
+
+ } +
+ + +
+

Error Handling

+
+
+ + +
+ + @if (ActionInput.FailureAction == "Skip") + { +
+ + +
+ } +
+
+
+
+ +@code { + [Parameter] public CustomValidationAction ActionInput { get; set; } = null!; + [Parameter] public EventCallback OnChange { get; set; } + [Parameter] public IEnumerable? TriggerParameters { get; set; } + [Parameter] public IEnumerable? FlowItems { get; set; } + + private async Task OnValueChanged() + { + await OnChange.InvokeAsync(); + } + + private async Task OnDataSourceChanged() + { + ActionInput.DataReference.Path = string.Empty; + ActionInput.DataReference.ActionId = null; + await OnValueChanged(); + } + + private async Task AddValidationRule() + { + ActionInput.ValidationRules.Add(new CustomValidationRule + { + Name = string.Empty, + Expression = string.Empty, + ErrorMessage = string.Empty + }); + await OnValueChanged(); + } + + private async Task RemoveValidationRule(CustomValidationRule rule) + { + ActionInput.ValidationRules.Remove(rule); + await OnValueChanged(); + } +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/W3cValidationComponent.razor b/Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/W3cValidationComponent.razor new file mode 100644 index 0000000..220228e --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Web/Components/Features/Actions/Validation/W3cValidationComponent.razor @@ -0,0 +1,204 @@ +@using Blocktrust.CredentialWorkflow.Core.Domain.Common +@using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation +@using WorkflowAction = Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Action + +@namespace Blocktrust.CredentialWorkflow.Web.Components.Features.Actions.Validation + +
+
+ +
+

Credential Source

+
+ + + @if (ActionInput.CredentialReference.Source == ParameterSource.Static) + { + + } + else if (ActionInput.CredentialReference.Source == ParameterSource.TriggerInput) + { + + } + else if (ActionInput.CredentialReference.Source == ParameterSource.ActionOutcome) + { + + } +
+
+ + +
+
+

Validation Rules

+ +
+ + @foreach (var rule in ActionInput.ValidationRules) + { +
+
+ + +
+ +
+ +

+ @GetConfigurationHelp(rule.Type) +

+
+
+ } +
+ + +
+

Error Handling

+
+
+ + +
+ + @if (ActionInput.FailureAction == "Skip") + { +
+ + +
+ } + +
+ + +
+
+
+
+
+ +@code { + [Parameter] public W3cValidationAction ActionInput { get; set; } = null!; + [Parameter] public EventCallback OnChange { get; set; } + [Parameter] public IEnumerable? TriggerParameters { get; set; } + [Parameter] public IEnumerable? FlowItems { get; set; } + + private async Task OnValueChanged() + { + await OnChange.InvokeAsync(); + } + + private async Task OnCredentialSourceChanged() + { + ActionInput.CredentialReference.Path = string.Empty; + ActionInput.CredentialReference.ActionId = null; + await OnValueChanged(); + } + + private async Task AddValidationRule() + { + ActionInput.ValidationRules.Add(new ValidationRule + { + Type = "Required", + Configuration = string.Empty + }); + await OnValueChanged(); + } + + private async Task RemoveValidationRule(ValidationRule rule) + { + ActionInput.ValidationRules.Remove(rule); + await OnValueChanged(); + } + + private string GetConfigurationHelp(string ruleType) => ruleType switch + { + "Required" => "Enter the field path that must be present (e.g., 'credentialSubject.id')", + "Format" => "Enter field path and expected format (e.g., 'credentialSubject.dateOfBirth:ISO8601')", + "Range" => "Enter field path and range (e.g., 'credentialSubject.age:18-65')", + "Custom" => "Enter custom validation expression", + _ => string.Empty + }; +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyEditor.razor b/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyEditor.razor index da0fa75..f3cd84f 100644 --- a/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyEditor.razor +++ b/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyEditor.razor @@ -3,42 +3,43 @@ @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Issuance @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Outgoing @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Verification +@using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Triggers -@using Blocktrust.CredentialWorkflow.Core.Domain.Workflow @using WorkflowAction = Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Action @using Blocktrust.CredentialWorkflow.Web.Components.Features.Triggers @using Blocktrust.CredentialWorkflow.Web.Components.Features.Actions @using Blocktrust.CredentialWorkflow.Web.Components.Features.Actions.Outgoing @using Blocktrust.CredentialWorkflow.Web.Components.Features.Actions.Issuance @using Blocktrust.CredentialWorkflow.Web.Components.Features.Actions.Verification +@using Blocktrust.CredentialWorkflow.Web.Components.Features.Actions.Validation -
+
-@if (Item is Trigger trigger) -{ - @switch (trigger.Type) + + @if (Item is Trigger trigger) { - case ETriggerType.HttpRequest: - @if (trigger.Input is TriggerInputHttpRequest request) - { - - } - break; - case ETriggerType.RecurringTimer: + @switch (trigger.Type) + { + case ETriggerType.HttpRequest: + @if (trigger.Input is TriggerInputHttpRequest request) + { + + } + break; + case ETriggerType.RecurringTimer: @if (trigger.Input is TriggerInputRecurringTimer recurringTimer) { } - break; case ETriggerType.WalletInteraction: @if (trigger.Input is TriggerInputWalletInteraction walletInteraction) @@ -47,7 +48,6 @@ TriggerInput="walletInteraction" OnChange="OnChange"/> } - break; case ETriggerType.ManualTrigger: @if (trigger.Input is TriggerInputManual manualTrigger) @@ -56,18 +56,16 @@ TriggerInput="manualTrigger" OnChange="OnChange"/> } - break; - - case ETriggerType.Form: - @if (trigger.Input is TriggerInputForm formTrigger) - { - - } - break; + case ETriggerType.Form: + @if (trigger.Input is TriggerInputForm formTrigger) + { + + } + break; } } else if (Item is WorkflowAction action) @@ -82,9 +80,7 @@ OnChange="OnChange" TriggerParameters="TriggerParameters"/> } - break; - case EActionType.IssueW3CSdCredential: @if (action.Input is IssueW3CSdCredential issueW3cSdCredential) { @@ -93,9 +89,7 @@ OnChange="OnChange" TriggerParameters="TriggerParameters"/> } - break; - case EActionType.DIDComm: @if (action.Input is DIDCommAction didCommAction) { @@ -105,9 +99,7 @@ TriggerParameters="TriggerParameters" FlowItems="FlowItems"/> } - break; - case EActionType.Http: @if (action.Input is HttpAction httpAction) { @@ -116,9 +108,7 @@ OnChange="OnChange" TriggerParameters="TriggerParameters"/> } - break; - case EActionType.Email: @if (action.Input is EmailAction emailAction) { @@ -128,7 +118,6 @@ TriggerParameters="TriggerParameters"/> } break; - case EActionType.VerifyW3CCredential: @if (action.Input is VerifyW3cCredential verifyW3cCredential) { @@ -139,7 +128,6 @@ FlowItems="FlowItems" /> } break; - case EActionType.VerifyW3CSdCredential: @if (action.Input is VerifyW3cSdCredential verifyW3cSdCredential) { @@ -147,9 +135,7 @@ ActionInput="verifyW3cSdCredential" OnChange="OnChange"/> } - break; - case EActionType.VerifyAnoncredCredential: @if (action.Input is VerifyAnoncredCredential verifyAnoncredCredential) { @@ -157,11 +143,32 @@ ActionInput="verifyAnoncredCredential" OnChange="OnChange"/> } - + break; + case EActionType.W3cValidation: + @if (action.Input is W3cValidationAction w3cValidationAction) + { + + } + break; + case EActionType.CustomValidation: + @if (action.Input is CustomValidationAction customValidationAction) + { + + } break; } }
+ @code { [Parameter] public object Item { get; set; } = null!; [Parameter] public string? Template { get; set; } diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyWindow.razor b/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyWindow.razor index 3704c81..756de85 100644 --- a/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyWindow.razor +++ b/Blocktrust.CredentialWorkflow.Web/Components/Features/PropertyWindow/PropertyWindow.razor @@ -5,6 +5,7 @@ @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Issuance @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Outgoing @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Verification +@using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Triggers @using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Triggers.HttpRequestsTemplates @using Blocktrust.CredentialWorkflow.Core.Domain.Workflow @@ -13,14 +14,12 @@ @if (SelectedItem == null) {

Add Item

-
touch_app

Triggers

-
- - @@ -43,7 +41,6 @@ OnClick="@(action => HandleItemClick(ETriggerType.RecurringTimer, action))" ShowAddButton="true"/> - @@ -59,14 +56,12 @@
-
smart_button

Actions

-
- @@ -95,7 +89,16 @@ OnClick="@(action => HandleItemClick(EActionType.VerifyAnoncredCredential, action))" ShowAddButton="true"/> - + + + + + @@ -120,9 +123,8 @@ OnChange="@OnChange" TriggerParameters="@GetTriggerParameters()" FlowItems="FlowItems" - WorkflowId="@workflowId"/> + WorkflowId="@workflowId"/> } - @if (_showError) {
@@ -153,11 +155,9 @@ private bool _isManualTriggersExpanded; private bool _isIssueCredentialExpanded; private bool _isVerifyCredentialExpanded; + private bool _isValidationExpanded; private bool _isOutgoingRequestExpanded; - private bool HasActions => FlowItems.Any(x => x is WorkflowAction); - private bool HasOutcome => FlowItems.Any(x => x is WorkflowOutcome); - private void ToggleGroup(string group) { switch (group) @@ -177,13 +177,67 @@ case "verifyCredential": _isVerifyCredentialExpanded = !_isVerifyCredentialExpanded; break; + case "validation": + _isValidationExpanded = !_isValidationExpanded; + break; case "outgoingRequest": _isOutgoingRequestExpanded = !_isOutgoingRequestExpanded; break; } } - private object CreateNewItem(object itemType, string template) + private IEnumerable? GetTriggerParameters() + { + var trigger = FlowItems.FirstOrDefault(x => x is Trigger) as Trigger; + if (trigger == null) + return null; + + if (trigger.Input is TriggerInputHttpRequest incomingRequest) + { + return incomingRequest.Parameters.Keys; + } + else if (trigger.Input is TriggerInputForm formTrigger) + { + return formTrigger.Parameters.Keys; + } + + return null; + } + + private async Task HandleItemClick(object itemType, MenuButton.MenuButtonAction action, string? template = null) + { + var newItem = CreateNewItem(itemType, template); + if (newItem != null) + { + await OnItemCreated.InvokeAsync(newItem); + if (action == MenuButton.MenuButtonAction.Configure) + { + await SelectItem(newItem); + } + } + } + + private async Task SelectItem(object? item) + { + SelectedItem = item; + StateHasChanged(); + } + + private void ShowError(string message) + { + _errorMessage = message; + _showError = true; + StateHasChanged(); + } + + private void CloseError() + { + _errorMessage = null; + _showError = false; + StateHasChanged(); + } + + private object CreateNewItem(object itemType, string? template = null) { try { @@ -196,7 +250,7 @@ } catch (Exception ex) { - HandleError(ex.Message); + ShowError(ex.Message); throw; } } @@ -217,7 +271,7 @@ Source = ParameterSource.TriggerInput, Path = "peerDid" }, - SenderPeerDid =new ParameterReference() + SenderPeerDid = new ParameterReference { Source = ParameterSource.AppSettings, Path = "DefaultSenderDid" @@ -252,38 +306,28 @@ }, Claims = new Dictionary() }, - EActionType.IssueW3CSdCredential => new IssueW3CSdCredential + EActionType.W3cValidation => new W3cValidationAction { Id = Guid.NewGuid(), - SubjectDid = new ParameterReference - { - Source = ParameterSource.TriggerInput, - Path = "subjectDid" - }, - IssuerDid = new ParameterReference + CredentialReference = new ParameterReference { - Source = ParameterSource.AppSettings, - Path = "DefaultIssuerDid" + Source = ParameterSource.TriggerInput }, - Claims = new Dictionary() + ValidationRules = new List(), + FailureAction = "Stop", + ErrorMessageTemplate = string.Empty }, - EActionType.IssueAnoncredCredential => new IssueAnoncredCredential + EActionType.CustomValidation => new CustomValidationAction { Id = Guid.NewGuid(), - SubjectDid = new ParameterReference + DataReference = new ParameterReference { - Source = ParameterSource.TriggerInput, - Path = "subjectDid" - }, - IssuerDid = new ParameterReference - { - Source = ParameterSource.AppSettings, - Path = "DefaultIssuerDid" + Source = ParameterSource.TriggerInput }, - CredentialDefinitionId = "", - Attributes = new Dictionary() + ValidationRules = new List(), + FailureAction = "Stop" }, - EActionType.VerifyW3CCredential => new VerifyW3cCredential +EActionType.VerifyW3CCredential => new VerifyW3cCredential { Id = Guid.NewGuid(), CheckSignature = true, @@ -316,57 +360,6 @@ }; } - private async Task HandleItemClick(object itemType, MenuButton.MenuButtonAction action, string? template = null) - { - var newItem = CreateNewItem(itemType, template); - if (newItem != null) - { - await OnItemCreated.InvokeAsync(newItem); - if (action == MenuButton.MenuButtonAction.Configure) - { - await SelectItem(newItem); - } - } - } - - private async Task SelectItem(object? item) - { - SelectedItem = item; - StateHasChanged(); - } - - private IEnumerable? GetTriggerParameters() - { - var trigger = FlowItems.FirstOrDefault(x => x is Trigger) as Trigger; - if (trigger == null) - return null; - - if (trigger.Input is TriggerInputHttpRequest incomingRequest) - { - return incomingRequest.Parameters.Keys; - } - else if (trigger.Input is TriggerInputForm formTrigger) - { - return formTrigger.Parameters.Keys; - } - - return null; - } - - private async Task HandleError(string message) - { - _errorMessage = message; - _showError = true; - StateHasChanged(); - } - - private void CloseError() - { - _errorMessage = null; - _showError = false; - StateHasChanged(); - } - private Trigger CreateNewTrigger(ETriggerType triggerType, string? template = null) { TriggerInput trigger; @@ -439,12 +432,10 @@ throw new ArgumentException($"Trigger type {triggerType} is not implemented."); } - return new Trigger { Type = triggerType, Input = trigger }; } - } \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css index 8d40d1e..aa4b458 100644 --- a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css +++ b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css @@ -1149,10 +1149,6 @@ video { overflow-y: auto; } -.overflow-x-hidden { - overflow-x: hidden; -} - .truncate { overflow: hidden; text-overflow: ellipsis; @@ -1859,12 +1855,6 @@ video { box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } -.ring-2 { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - .ring-inset { --tw-ring-inset: inset; } @@ -1874,11 +1864,6 @@ video { --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); } -.ring-slate-500 { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity)); -} - .transition { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; @@ -2056,11 +2041,6 @@ a { color: rgb(17 24 39 / var(--tw-text-opacity)); } -.hover\:text-green-800:hover { - --tw-text-opacity: 1; - color: rgb(22 101 52 / var(--tw-text-opacity)); -} - .hover\:text-green-900:hover { --tw-text-opacity: 1; color: rgb(20 83 45 / var(--tw-text-opacity)); @@ -2081,11 +2061,6 @@ a { color: rgb(51 65 85 / var(--tw-text-opacity)); } -.hover\:text-slate-900:hover { - --tw-text-opacity: 1; - color: rgb(15 23 42 / var(--tw-text-opacity)); -} - .hover\:opacity-80:hover { opacity: 0.8; } From eb2646739bc9264069c947836a6d4885e7ac1ad0 Mon Sep 17 00:00:00 2001 From: ndigirigijohn Date: Mon, 24 Feb 2025 11:59:21 +0300 Subject: [PATCH 2/2] validation actions execution --- .../Blocktrust.CredentialWorkflow.Core.csproj | 1 + .../CustomValidationHandler.cs | 55 ++++++ .../CustomValidationRequest.cs | 35 ++++ .../W3cValidation/W3cValidationHandler.cs | 157 ++++++++++++++++++ .../W3cValidation/W3cValidationRequest.cs | 35 ++++ .../ExecuteWorkflow/ExecuteWorkflowHandler.cs | 129 ++++++++++++++ 6 files changed, 412 insertions(+) create mode 100644 Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationHandler.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationRequest.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationHandler.cs create mode 100644 Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationRequest.cs diff --git a/Blocktrust.CredentialWorkflow.Core/Blocktrust.CredentialWorkflow.Core.csproj b/Blocktrust.CredentialWorkflow.Core/Blocktrust.CredentialWorkflow.Core.csproj index 1a33a4f..532a667 100644 --- a/Blocktrust.CredentialWorkflow.Core/Blocktrust.CredentialWorkflow.Core.csproj +++ b/Blocktrust.CredentialWorkflow.Core/Blocktrust.CredentialWorkflow.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationHandler.cs b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationHandler.cs new file mode 100644 index 0000000..750f065 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationHandler.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using FluentResults; +using Jint; +using MediatR; + +namespace Blocktrust.CredentialWorkflow.Core.Commands.ValidateCredentials.CustomValidation; + +public class CustomValidationHandler : IRequestHandler> +{ + public async Task> Handle(CustomValidationRequest request, CancellationToken cancellationToken) + { + var result = new CustomValidationResult(); + var engine = new Engine(); + + try + { + // Convert data to JSON string for JavaScript engine + var dataJson = JsonSerializer.Serialize(request.Data); + + // Set up the JavaScript environment + engine.SetValue("data", JsonSerializer.Deserialize(dataJson)); + + foreach (var rule in request.Rules) + { + try + { + // Execute each validation rule + var isValid = engine.Evaluate(rule.Expression).AsBoolean(); + + if (!isValid) + { + result.Errors.Add(new CustomValidationError( + rule.Name, + rule.ErrorMessage ?? $"Validation rule '{rule.Name}' failed" + )); + } + } + catch (Exception ex) + { + result.Errors.Add(new CustomValidationError( + rule.Name, + $"Error evaluating rule: {ex.Message}" + )); + } + } + + result.IsValid = !result.Errors.Any(); + return Result.Ok(result); + } + catch (Exception ex) + { + return Result.Fail($"Validation failed: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationRequest.cs b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationRequest.cs new file mode 100644 index 0000000..619e50e --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/CustomValidation/CustomValidationRequest.cs @@ -0,0 +1,35 @@ +using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; +using FluentResults; +using MediatR; + +namespace Blocktrust.CredentialWorkflow.Core.Commands.ValidateCredentials.CustomValidation; + +public class CustomValidationRequest : IRequest> +{ + public object Data { get; } + public List Rules { get; } + + public CustomValidationRequest(object data, List rules) + { + Data = data; + Rules = rules; + } +} + +public class CustomValidationResult +{ + public bool IsValid { get; set; } + public List Errors { get; set; } = new(); +} + +public class CustomValidationError +{ + public string RuleName { get; set; } + public string Message { get; set; } + + public CustomValidationError(string ruleName, string message) + { + RuleName = ruleName; + Message = message; + } +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationHandler.cs b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationHandler.cs new file mode 100644 index 0000000..e9b017c --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationHandler.cs @@ -0,0 +1,157 @@ +using System.Text.Json; +using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; +using FluentResults; +using MediatR; + +namespace Blocktrust.CredentialWorkflow.Core.Commands.ValidateCredentials.W3cValidation; + +public class W3cValidationHandler : IRequestHandler> +{ + public async Task> Handle(W3cValidationRequest request, CancellationToken cancellationToken) + { + var result = new ValidationResult(); + try + { + var credentialJson = JsonDocument.Parse(request.Credential); + + foreach (var rule in request.Rules) + { + switch (rule.Type.ToLower()) + { + case "required": + ValidateRequiredField(credentialJson, rule, result); + break; + case "format": + ValidateFormat(credentialJson, rule, result); + break; + case "range": + ValidateRange(credentialJson, rule, result); + break; + case "custom": + ValidateCustomRule(credentialJson, rule, result); + break; + } + } + + result.IsValid = !result.Errors.Any(); + return Result.Ok(result); + } + catch (JsonException ex) + { + return Result.Fail($"Invalid credential format: {ex.Message}"); + } + catch (Exception ex) + { + return Result.Fail($"Validation failed: {ex.Message}"); + } + } + + private void ValidateRequiredField(JsonDocument credential, ValidationRule rule, ValidationResult result) + { + var path = rule.Configuration; + var pathParts = path.Split('.'); + var element = credential.RootElement; + + foreach (var part in pathParts) + { + if (!element.TryGetProperty(part, out var child)) + { + result.Errors.Add(new ValidationError("Required", $"Required field '{path}' is missing")); + return; + } + element = child; + } + } + + private void ValidateFormat(JsonDocument credential, ValidationRule rule, ValidationResult result) + { + var config = rule.Configuration.Split(':'); + if (config.Length != 2) + { + result.Errors.Add(new ValidationError("Format", "Invalid format rule configuration")); + return; + } + + var path = config[0]; + var format = config[1]; + var pathParts = path.Split('.'); + var element = credential.RootElement; + + foreach (var part in pathParts) + { + if (!element.TryGetProperty(part, out var child)) + { + result.Errors.Add(new ValidationError("Format", $"Field '{path}' not found")); + return; + } + element = child; + } + + // Validate format based on the format type + switch (format.ToUpper()) + { + case "ISO8601": + if (!DateTime.TryParse(element.GetString(), out _)) + { + result.Errors.Add(new ValidationError("Format", $"Field '{path}' is not a valid ISO8601 date")); + } + break; + // Add more format validations as needed + } + } + + private void ValidateRange(JsonDocument credential, ValidationRule rule, ValidationResult result) + { + var config = rule.Configuration.Split(':'); + if (config.Length != 2) + { + result.Errors.Add(new ValidationError("Range", "Invalid range rule configuration")); + return; + } + + var path = config[0]; + var range = config[1].Split('-'); + if (range.Length != 2) + { + result.Errors.Add(new ValidationError("Range", "Invalid range format")); + return; + } + + if (!decimal.TryParse(range[0], out var min) || !decimal.TryParse(range[1], out var max)) + { + result.Errors.Add(new ValidationError("Range", "Invalid range values")); + return; + } + + var pathParts = path.Split('.'); + var element = credential.RootElement; + + foreach (var part in pathParts) + { + if (!element.TryGetProperty(part, out var child)) + { + result.Errors.Add(new ValidationError("Range", $"Field '{path}' not found")); + return; + } + element = child; + } + + if (!decimal.TryParse(element.GetString(), out var value)) + { + result.Errors.Add(new ValidationError("Range", $"Field '{path}' is not a valid number")); + return; + } + + if (value < min || value > max) + { + result.Errors.Add(new ValidationError("Range", $"Field '{path}' value {value} is outside range {min}-{max}")); + } + } + + private void ValidateCustomRule(JsonDocument credential, ValidationRule rule, ValidationResult result) + { + // Custom rule validation - can be extended based on requirements + // For now, just log that custom validation is not implemented + result.Errors.Add(new ValidationError("Custom", "Custom validation rules are not implemented")); + } +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationRequest.cs b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationRequest.cs new file mode 100644 index 0000000..bc854c3 --- /dev/null +++ b/Blocktrust.CredentialWorkflow.Core/Commands/ValidateCredentials/W3cValidation/W3cValidationRequest.cs @@ -0,0 +1,35 @@ +using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; +using FluentResults; +using MediatR; + +namespace Blocktrust.CredentialWorkflow.Core.Commands.ValidateCredentials.W3cValidation; + +public class W3cValidationRequest : IRequest> +{ + public string Credential { get; } + public List Rules { get; } + + public W3cValidationRequest(string credential, List rules) + { + Credential = credential; + Rules = rules; + } +} + +public class ValidationResult +{ + public bool IsValid { get; set; } + public List Errors { get; set; } = new(); +} + +public class ValidationError +{ + public string RuleType { get; set; } + public string Message { get; set; } + + public ValidationError(string ruleType, string message) + { + RuleType = ruleType; + Message = message; + } +} \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Core/Commands/Workflow/ExecuteWorkflow/ExecuteWorkflowHandler.cs b/Blocktrust.CredentialWorkflow.Core/Commands/Workflow/ExecuteWorkflow/ExecuteWorkflowHandler.cs index 212e917..520c3f9 100644 --- a/Blocktrust.CredentialWorkflow.Core/Commands/Workflow/ExecuteWorkflow/ExecuteWorkflowHandler.cs +++ b/Blocktrust.CredentialWorkflow.Core/Commands/Workflow/ExecuteWorkflow/ExecuteWorkflowHandler.cs @@ -28,7 +28,10 @@ using System.Text.RegularExpressions; using Blocktrust.Common.Resolver; using Blocktrust.CredentialWorkflow.Core.Commands.DIDComm.GetPeerDIDs; +using Blocktrust.CredentialWorkflow.Core.Commands.ValidateCredentials.CustomValidation; +using Blocktrust.CredentialWorkflow.Core.Commands.ValidateCredentials.W3cValidation; using Blocktrust.CredentialWorkflow.Core.Commands.Workflow.SendEmailAction; +using Blocktrust.CredentialWorkflow.Core.Domain.ProcessFlow.Actions.Validation; using Blocktrust.DIDComm.Message.Attachments; using Blocktrust.DIDComm.Message.Messages; using Blocktrust.Mediator.Client.Commands.ForwardMessage; @@ -166,6 +169,41 @@ public async Task> Handle(ExecuteWorkflowRequest request, Cancellat } break; } + + case EActionType.W3cValidation: + { + var result = await ProcessW3cValidationAction( + action, + actionOutcome, + workflowOutcomeId, + executionContext, + actionOutcomes, + workflow, + cancellationToken + ); + if (result.IsFailed || result.Value.Equals(false)) + { + return result; + } + break; + } + case EActionType.CustomValidation: + { + var result = await ProcessCustomValidationAction( + action, + actionOutcome, + workflowOutcomeId, + executionContext, + actionOutcomes, + workflow, + cancellationToken + ); + if (result.IsFailed || result.Value.Equals(false)) + { + return result; + } + break; + } default: { return Result.Fail($"The action type {action.Type} is not supported."); @@ -501,6 +539,97 @@ CancellationToken cancellationToken return Result.Ok(true); } + + + + private async Task> ProcessW3cValidationAction( + Action action, + ActionOutcome actionOutcome, + Guid workflowOutcomeId, + ExecutionContext executionContext, + List actionOutcomes, + Domain.Workflow.Workflow workflow, + CancellationToken cancellationToken +) +{ + var input = (W3cValidationAction)action.Input; + + var credentialStr = await GetParameterFromExecutionContext(input.CredentialReference, executionContext, workflow, actionOutcomes, EActionType.W3cValidation); + if (string.IsNullOrWhiteSpace(credentialStr)) + { + var errorMessage = "No credential found in the execution context to validate."; + return await FinishActionsWithFailure(workflowOutcomeId, actionOutcome, errorMessage, actionOutcomes, cancellationToken); + } + + var validationRequest = new W3cValidationRequest(credentialStr, input.ValidationRules); + var validationResult = await _mediator.Send(validationRequest, cancellationToken); + + if (validationResult.IsFailed) + { + var errorMessage = string.Join(", ", validationResult.Errors.Select(e => e.Message)); + return await FinishActionsWithFailure(workflowOutcomeId, actionOutcome, errorMessage, actionOutcomes, cancellationToken); + } + + var result = validationResult.Value; + if (!result.IsValid) + { + var errorMessage = string.Join(", ", result.Errors.Select(e => $"{e.RuleType}: {e.Message}")); + + if (input.FailureAction == "Stop") + { + return await FinishActionsWithFailure(workflowOutcomeId, actionOutcome, errorMessage, actionOutcomes, cancellationToken); + } + // Handle Skip and Continue cases as needed + } + + actionOutcome.FinishOutcomeWithSuccess(JsonSerializer.Serialize(result)); + return Result.Ok(true); +} + + private async Task> ProcessCustomValidationAction( + Action action, + ActionOutcome actionOutcome, + Guid workflowOutcomeId, + ExecutionContext executionContext, + List actionOutcomes, + Domain.Workflow.Workflow workflow, + CancellationToken cancellationToken + ) + { + var input = (CustomValidationAction)action.Input; + + var dataStr = await GetParameterFromExecutionContext(input.DataReference, executionContext, workflow, actionOutcomes, EActionType.CustomValidation); + if (string.IsNullOrWhiteSpace(dataStr)) + { + var errorMessage = "No data found in the execution context to validate."; + return await FinishActionsWithFailure(workflowOutcomeId, actionOutcome, errorMessage, actionOutcomes, cancellationToken); + } + + var data = JsonSerializer.Deserialize(dataStr); + var validationRequest = new CustomValidationRequest(data, input.ValidationRules); + var validationResult = await _mediator.Send(validationRequest, cancellationToken); + + if (validationResult.IsFailed) + { + var errorMessage = string.Join(", ", validationResult.Errors.Select(e => e.Message)); + return await FinishActionsWithFailure(workflowOutcomeId, actionOutcome, errorMessage, actionOutcomes, cancellationToken); + } + + var result = validationResult.Value; + if (!result.IsValid) + { + var errorMessage = string.Join(", ", result.Errors.Select(e => $"{e.RuleName}: {e.Message}")); + + if (input.FailureAction == "Stop") + { + return await FinishActionsWithFailure(workflowOutcomeId, actionOutcome, errorMessage, actionOutcomes, cancellationToken); + } + // Handle Skip and Continue cases as needed + } + + actionOutcome.FinishOutcomeWithSuccess(JsonSerializer.Serialize(result)); + return Result.Ok(true); + } public string ProcessEmailTemplate(string template, Dictionary parameters) {